【华为面试手撕代码】
文章目錄
- 常用的排序算法
- 1.冒泡排序
- 2.選擇排序
- 3.插入排序
- 4.快排排序
- 5.歸并排序
- 6.堆排序
- Java 的sort基于什么實(shí)現(xiàn)
- 排序算法原理,何為穩(wěn)定不穩(wěn)定,快排是否穩(wěn)定
- 查找
- 二分查找
- 復(fù)盤筆試題
- 3.尋找重復(fù)的子樹
- 樹的遍歷方式
- 樹的遍歷方式(先序、中序、后序)
- 先序
- 中序
- 后序
- 如何用數(shù)組模擬二叉樹的遍歷過程?
- 求二叉樹的深度兩種方法
- 棧、隊(duì)列
- 232. 用棧實(shí)現(xiàn)隊(duì)列
- 225. 用隊(duì)列實(shí)現(xiàn)棧
- 字符串
- 序列化與反序列化
- 統(tǒng)計(jì)字母出現(xiàn)次數(shù)從大到小排序
- 字符串中的最長不重復(fù)子串
- 動(dòng)態(tài)規(guī)劃
- 跳臺(tái)階
- 最長公共子序列
- 鏈表
- 反轉(zhuǎn)鏈表
- leetcode 445. 兩數(shù)相加 II
- 尋找字符串最長回文串
- 力扣14. 最長公共前綴
- 1701 平均等待時(shí)間
先說思路,然后寫代碼
常用的排序算法
1.冒泡排序
1.設(shè)置循環(huán)次數(shù)。
2.設(shè)置開始比較的位數(shù),和結(jié)束的位數(shù)。
3.兩兩比較,將最小的放到前面去。
重復(fù)2、3步,直到循環(huán)次數(shù)完畢。
2.選擇排序
1.首先在未排序序列中找到最小元素,存放到排序序列的起始位置。
2.再從剩余未排序元素中繼續(xù)尋找最小元素,然后放到已排序序列的末尾。
3.重復(fù)第二步,直到所有元素均排序完畢。
3.插入排序
將數(shù)組R劃分成兩個(gè)子區(qū)間R[1..i-1](已排好序的有序區(qū))和R[i..n](當(dāng)前未排序的部分,可稱無序區(qū))。插入排序的基本操作是將當(dāng)前無序區(qū)的第1個(gè)記錄R[i]插人到有序區(qū)R[1..i-1]中適當(dāng)?shù)奈恢蒙?#xff0c;使R[1..i]變?yōu)樾碌挠行騾^(qū)
public voidInsertSort () {int i,j,temp;for(i=1;i<array.length;i++) {// R[i..n](當(dāng)前未排序的部分,可稱無序區(qū))temp=array[i];for(j=i-1;j>=0;j--) {// R[1..i-1](已排好序的有序區(qū))if(temp>array[j]) {break;}else {array[j+1]=array[j];//往后挪,騰位置}}array[j+1]=temp;//插入數(shù)據(jù)} }4.快排排序
先從右往左找到一個(gè)小于基準(zhǔn)數(shù)的數(shù),再從左往右找到一個(gè)大于基準(zhǔn)數(shù)的數(shù),交換他們.重復(fù),當(dāng)ij相遇時(shí)把此時(shí)這個(gè)數(shù)和基準(zhǔn)數(shù)交換,再分別處理左右兩邊的數(shù)字.
== 先從右往左找,再從左往右找==
5.歸并排序
假設(shè)初始序列含有n個(gè)記錄,則可以看成是n個(gè)有序的子序列,每個(gè)子序列的長度為1,然后兩兩歸并,得到n/2個(gè)長度為2(或者是1)的有序子序列,再兩兩歸并。如此重復(fù),直到得到一個(gè)長度為n的有序序列為止。
private static void sort(int[] array, int left, int right) {if (left == right) {return;}int mid = left + ((right - left)/2);sort(array, left, mid); // 對左側(cè)子序列進(jìn)行遞歸排序sort(array, mid + 1, right); // 對右側(cè)子序列進(jìn)行遞歸排序merge(array, left, mid, right); // 合并 }private static void merge(int[] array, int left, int mid, int right) {int[] temp = new int[right - left + 1];int i = 0;int p1 = left;int p2 = mid + 1;// 比較左右兩部分的元素,哪個(gè)小,把那個(gè)元素填入temp中while (p1 <= mid && p2 <= right) {temp[i++] = array[p1] < array[p2] ? array[p1++] : array[p2++];}// 上面的循環(huán)退出后,把剩余元素依次填入temp(以下兩個(gè)while只有一個(gè)會(huì)執(zhí)行)while (p1 <= mid) {temp[i++] = array[p1++];}while (p2 <= right) {temp[i++] = array[p2++];}// 把最終的排序的結(jié)果復(fù)制給原數(shù)組for (i = 0; i < temp.length; i++) {array[left + i] = temp[i];} }6.堆排序
可以將堆看做是一個(gè)完全二叉樹。并且,每個(gè)結(jié)點(diǎn)的值都大于等于其左右孩子結(jié)點(diǎn)的值,稱為大頂堆;或者每個(gè)結(jié)點(diǎn)的值都小于等于其左右孩子結(jié)點(diǎn)的值,稱為小頂堆。
堆排序(Heap Sort)是利用堆進(jìn)行排序的方法。其基本思想為:將待排序列構(gòu)造成一個(gè)大頂堆(或小頂堆),整個(gè)序列的最大值(或最小值)就是堆頂?shù)母Y(jié)點(diǎn),將根節(jié)點(diǎn)的值和堆數(shù)組的末尾元素交換,此時(shí)末尾元素就是最大值(或最小值),然后將剩余的n-1個(gè)序列重新構(gòu)造成一個(gè)堆,這樣就會(huì)得到n個(gè)元素中的次大值(或次小值),如此反復(fù)執(zhí)行,最終得到一個(gè)有序序列。
圖解
a.將無需序列構(gòu)建成一個(gè)堆,根據(jù)升序降序需求選擇大頂堆或小頂堆;
b.將堆頂元素與末尾元素交換,將最大元素"沉"到數(shù)組末端;
c.重新調(diào)整結(jié)構(gòu),使其滿足堆定義,然后繼續(xù)交換堆頂元素與當(dāng)前末尾元素,反復(fù)執(zhí)行調(diào)整+交換步驟,直到整個(gè)序列有序。
作者:尼小摩
鏈接:https://www.jianshu.com/p/a161b991fa82
來源:簡書
著作權(quán)歸作者所有。商業(yè)轉(zhuǎn)載請聯(lián)系作者獲得授權(quán),非商業(yè)轉(zhuǎn)載請注明出處。
Java 的sort基于什么實(shí)現(xiàn)
(快排),全都是快排嗎(不知道)
排序算法原理,何為穩(wěn)定不穩(wěn)定,快排是否穩(wěn)定
查找
二分查找
時(shí)間復(fù)雜度(logn)
復(fù)盤筆試題
筆試題的思路(說說上次筆試第二題為啥沒全對??(答:估摸著邊界沒處理好)
3.尋找重復(fù)的子樹
題目:
給定一棵二叉樹 root,返回所有重復(fù)的子樹。
對于同一類的重復(fù)子樹,你只需要返回其中任意一棵的根結(jié)點(diǎn)即可。
如果兩棵樹具有相同的結(jié)構(gòu)和相同的結(jié)點(diǎn)值,則它們是重復(fù)的
思路
序列化二叉樹。
序列化結(jié)果為 1,2,#,#,3,4,#,#,5,#,#。每棵不同子樹的序列化結(jié)果都是唯一的。
算法
使用深度優(yōu)先搜索,其中遞歸函數(shù)返回當(dāng)前子樹的序列化結(jié)果。把每個(gè)節(jié)點(diǎn)開始的子樹序列化結(jié)果保存在 mapmap 中,然后判斷是否存在重復(fù)的子樹。
樹的遍歷方式
樹的遍歷方式(先序、中序、后序)
先序
遞歸
// 前序遍歷·遞歸·LC144_二叉樹的前序遍歷 class Solution {ArrayList<Integer> preOrderReverse(TreeNode root) {ArrayList<Integer> result = new ArrayList<Integer>();preOrder(root, result);return result;}void preOrder(TreeNode root, ArrayList<Integer> result) {if (root == null) {return;}result.add(root.val); // 注意這一句preOrder(root.left, result);preOrder(root.right, result);} }非遞歸
// 前序遍歷順序:中-左-右,入棧順序:中-右-左 class Solution {public List<Integer> preorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null){return result;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()){TreeNode node = stack.pop();result.add(node.val);if (node.right != null){stack.push(node.right);}if (node.left != null){stack.push(node.left);}}return result;} }中序
遞歸
// 中序遍歷·遞歸·LC94_二叉樹的中序遍歷 class Solution {public List<Integer> inorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();inorder(root, res);return res;}void inorder(TreeNode root, List<Integer> list) {if (root == null) {return;}inorder(root.left, list);list.add(root.val); // 注意這一句inorder(root.right, list);} }非遞歸
class Solution {public List<Integer> inorderTraversal(TreeNode root) {Stack<TreeNode> stack=new Stack<>();List<Integer> list=new ArrayList<>();TreeNode cur=root;while (cur!=null || !stack.isEmpty()){while(cur!=null){stack.push(cur);cur=cur.left;}cur=stack.pop();list.add(cur.val);cur=cur.right; }return list;} }后序
遞歸
// 后序遍歷·遞歸·LC145_二叉樹的后序遍歷 class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> res = new ArrayList<>();postorder(root, res);return res;}void postorder(TreeNode root, List<Integer> list) {if (root == null) {return;}postorder(root.left, list);postorder(root.right, list);list.add(root.val); // 注意這一句} }非遞歸
// 后序遍歷順序 左-右-中 入棧順序:中-左-右 出棧順序:中-右-左, 最后翻轉(zhuǎn)結(jié)果 class Solution {public List<Integer> postorderTraversal(TreeNode root) {List<Integer> result = new ArrayList<>();if (root == null){return result;}Stack<TreeNode> stack = new Stack<>();stack.push(root);while (!stack.isEmpty()){TreeNode node = stack.pop();result.add(node.val);if (node.left != null){stack.push(node.left);}if (node.right != null){stack.push(node.right);}}Collections.reverse(result);return result;} }如何用數(shù)組模擬二叉樹的遍歷過程?
順序二叉樹的滿足條件:
1.一般指完全二叉樹
2.第n個(gè)元素的左子樹為2n+1;
3.第n個(gè)元素的右子樹為2n+2;
4.第n個(gè)子樹的父節(jié)點(diǎn)為(n-1)/2;
注意:n為數(shù)組下標(biāo)所以是從0開始
求二叉樹的深度兩種方法
方法一:深度優(yōu)先搜索
左子樹和右子樹的最大深度分別為 l 和 r,則二叉樹的最大深度即為 max(l,r)+1
而左子樹和右子樹的最大深度又可以以同樣的方式進(jìn)行計(jì)算。因此用「深度優(yōu)先搜索」方法計(jì)算二叉樹的最大深度。
在計(jì)算當(dāng)前二叉樹的最大深度時(shí),先遞歸計(jì)算出其左子樹和右子樹的最大深度,然后在 O(1)時(shí)間內(nèi)計(jì)算出當(dāng)前二叉樹的最大深度。遞歸在訪問到空節(jié)點(diǎn)時(shí)退出。
方法二:廣度優(yōu)先搜索
廣度優(yōu)先搜索的隊(duì)列里存放的是「當(dāng)前層的所有節(jié)點(diǎn)」。每次拓展下一層的時(shí)候,每次只從隊(duì)列里拿出一個(gè)節(jié)點(diǎn),需要將隊(duì)列里的所有節(jié)點(diǎn)都拿出來進(jìn)行拓展,這樣能保證每次拓展完的時(shí)候隊(duì)列里存放的是當(dāng)前層的所有節(jié)點(diǎn),即一層一層地進(jìn)行拓展,最后用一個(gè)變量ans 來維護(hù)拓展的次數(shù),該二叉樹的最大深度即為ans。
棧、隊(duì)列
232. 用棧實(shí)現(xiàn)隊(duì)列
參考網(wǎng)址
入隊(duì)(push)
一個(gè)隊(duì)列是 FIFO 的,但一個(gè)棧是 LIFO 的。這就意味著最新壓入的元素必須得放在棧底。為了實(shí)現(xiàn)這個(gè)目的,我們首先需要把 s1 中所有的元素移到 s2 中,接著把新元素壓入 s2。最后把 s2 中所有的元素彈出,再把彈出的元素壓入 s1。
出隊(duì)(pop)
直接從 s1 彈出就可以了,因?yàn)?s1 的棧頂元素就是隊(duì)列的隊(duì)首元素。同時(shí)我們把彈出之后 s1 的棧頂元素賦值給代表隊(duì)首元素的 front 變量。
判斷空(empty)
s1 存儲(chǔ)了隊(duì)列所有的元素,所以只需要檢查 s1 的是否為空就可以了。
取隊(duì)首元素(peek)
在我們的算法中,用了 front 變量來存儲(chǔ)隊(duì)首元素,在每次 入隊(duì) 操作或者 出隊(duì) 操作之后這個(gè)變量都會(huì)隨之更新。
入隊(duì)(push)
新元素總是壓入 s1 的棧頂,同時(shí)我們會(huì)把 s1 中壓入的第一個(gè)元素賦值給作為隊(duì)首元素的 front 變量。
出隊(duì)(pop)
根據(jù)棧 LIFO 的特性,s1 中第一個(gè)壓入的元素在棧底。為了彈出 s1 的棧底元素,我們得把 s1 中所有的元素全部彈出,再把它們壓入到另一個(gè)棧 s2 中,這個(gè)操作會(huì)讓元素的入棧順序反轉(zhuǎn)過來。通過這樣的方式,s1 中棧底元素就變成了 s2 的棧頂元素,這樣就可以直接從 s2 將它彈出了。一旦 s2 變空了,我們只需把 s1 中的元素再一次轉(zhuǎn)移到 s2 就可以了。
判斷空(empty)
s1 和 s2 都存有隊(duì)列的元素,所以只需要檢查 s1 和 s2 是否都為空就可以了。
取隊(duì)首元素(peek)
我們定義了 front 變量來保存隊(duì)首元素,每次 入隊(duì) 操作我們都會(huì)隨之更新這個(gè)變量。當(dāng) s2 為空,front 變量就是隊(duì)首元素,當(dāng) s2 非空,s2 的棧頂元素就是隊(duì)首元素。
225. 用隊(duì)列實(shí)現(xiàn)棧
為了滿足棧的特性,即最后入棧的元素最先出棧,在使用隊(duì)列實(shí)現(xiàn)棧時(shí),應(yīng)滿足隊(duì)列前端的元素是最后入棧的元素。可以使用兩個(gè)隊(duì)列實(shí)現(xiàn)棧的操作,其中queue1用于存儲(chǔ)棧內(nèi)的元素,queue2作為入棧操作的輔助隊(duì)列。
入棧操作
首先將元素入隊(duì)到queue2,然后將queue1的全部元素依次出隊(duì)并入隊(duì)到queue2,此時(shí)queue 2的前端的元素即為新入棧的元素,再將queue1和queue2互換,則 queue1的元素即為棧內(nèi)的元素,queue1 的前端和后端分別對應(yīng)棧頂和棧底。
出棧操作
由于每次入棧操作都確保 queue1?的前端元素為棧頂元素,因此出棧操作和獲得棧頂元素操作都可以簡單實(shí)現(xiàn)。出棧操作只需要移除queue1的前端元素并返回即可,
獲得棧頂元素操作
只需要獲得queue1的前端元素并返回即可(不移除元素)。
判斷棧是否為空
由于queue1用于存儲(chǔ)棧內(nèi)的元素,判斷棧是否為空時(shí),只需要判斷 queue1是否為空即可。
字符串
序列化與反序列化
題目:
序列化是將一個(gè)數(shù)據(jù)結(jié)構(gòu)或者對象轉(zhuǎn)換為連續(xù)的比特位的操作,進(jìn)而可以將轉(zhuǎn)換后的數(shù)據(jù)存儲(chǔ)在一個(gè)文件或者內(nèi)存中,同時(shí)也可以通過網(wǎng)絡(luò)傳輸?shù)搅硪粋€(gè)計(jì)算機(jī)環(huán)境,采取相反方式重構(gòu)得到原數(shù)據(jù)。
請?jiān)O(shè)計(jì)一個(gè)算法來實(shí)現(xiàn)二叉樹的序列化與反序列化。這里不限定你的序列 / 反序列化算法執(zhí)行邏輯,你只需要保證一個(gè)二叉樹可以被序列化為一個(gè)字符串并且將這個(gè)字符串反序列化為原始的樹結(jié)構(gòu)。
思路: 先序遍歷的遞歸思想
1.序列化:先序遍歷結(jié)果(StringBuilder節(jié)約空間)
2.反序列化:將String類型轉(zhuǎn)化為二叉樹,
?????
統(tǒng)計(jì)字母出現(xiàn)次數(shù)從大到小排序
給出一個(gè)只包含字母的字符串,
不包含空格,統(tǒng)計(jì)字符串中各個(gè)子字母(區(qū)分大小寫)出現(xiàn)的次數(shù),
并按照字母出現(xiàn)次數(shù)從大到小的順序輸出各個(gè)字母及其出現(xiàn)次數(shù)
如果次數(shù)相同,按照自然順序排序,且小寫字母在大寫字母之前
字符串中的最長不重復(fù)子串
「滑動(dòng)窗口」
動(dòng)態(tài)規(guī)劃
跳臺(tái)階
public class pr_5_1 {public static void main(String[] args){Scanner in=new Scanner(System.in);while (in.hasNext()){int n= in.nextInt();int res=find(n);System.out.print(res);}}public static int find(int n){//動(dòng)態(tài)規(guī)劃int count=0;if(n==1||n==2) return 1;if(n==3) return 2;if(n>3) count=find(n-1)+find(n-3);return count;} } public class pr_5 {public static void main(String[] args){Scanner in=new Scanner(System.in);while (in.hasNext()){int n= in.nextInt();find(0,n);System.out.print(count);}}public static int count=0;public static void find(int cur,int n){//遞歸if(cur==n){count++;return ;}if(cur>n) return ;find(cur+1,n);find(cur+3,n);} }最長公共子序列
(1) i == 0 || j == 0 LCS(i, j) = 0
(2) Xi ==Yj LCS(i, j) = LCS(i - 1,j- 1) + 1
(3) Xi !=Yj LCS(i, j) = max(LCS(i – 1, j) , LCS(i, j – 1))
鏈表
反轉(zhuǎn)鏈表
題目:
給定單鏈表的頭節(jié)點(diǎn) head ,請反轉(zhuǎn)鏈表,并返回反轉(zhuǎn)后的鏈表的頭節(jié)點(diǎn)。
方法一:迭代
class Solution {public ListNode reverseList(ListNode head) {ListNode prev = null;ListNode curr = head;while (curr != null) {ListNode next = curr.next;curr.next = prev;prev = curr;curr = next;}return prev;} }方法二:遞歸
遞歸版本稍微復(fù)雜一些,其關(guān)鍵在于反向工作。假設(shè)鏈表的其余部分已經(jīng)被反轉(zhuǎn),現(xiàn)在應(yīng)該如何反轉(zhuǎn)它前面的部分?
leetcode 445. 兩數(shù)相加 II
題目:
給你兩個(gè) 非空 鏈表來代表兩個(gè)非負(fù)整數(shù)。數(shù)字最高位位于鏈表開始位置。它們的每個(gè)節(jié)點(diǎn)只存儲(chǔ)一位數(shù)字。將這兩數(shù)相加會(huì)返回一個(gè)新的鏈表。
你可以假設(shè)除了數(shù)字 0 之外,這兩個(gè)數(shù)字都不會(huì)以零開頭。
思路與算法 : 棧
鏈表中數(shù)位的順序與做加法的順序是相反的,為了逆序處理所有數(shù)位,使用棧:把所有數(shù)字壓入棧中,再依次取出相加。計(jì)算過程中需要注意進(jìn)位的情況。
尋找字符串最長回文串
思路:
動(dòng)態(tài)規(guī)劃的邊界條件:
public class Solution {public String longestPalindrome(String s) {int len = s.length();if (len < 2) {return s;}int maxLen = 1;int begin = 0;// dp[i][j] 表示 s[i..j] 是否是回文串boolean[][] dp = new boolean[len][len];// 初始化:所有長度為 1 的子串都是回文串for (int i = 0; i < len; i++) {dp[i][i] = true;}char[] charArray = s.toCharArray();// 遞推開始// 先枚舉子串長度for (int L = 2; L <= len; L++) {// 枚舉左邊界,左邊界的上限設(shè)置可以寬松一些for (int i = 0; i < len; i++) {// 由 L 和 i 可以確定右邊界,即 j - i + 1 = L 得int j = L + i - 1;// 如果右邊界越界,就可以退出當(dāng)前循環(huán)if (j >= len) {break;}if (charArray[i] != charArray[j]) {dp[i][j] = false;} else {if (j - i < 3) {dp[i][j] = true;} else {dp[i][j] = dp[i + 1][j - 1];}}// 只要 dp[i][L] == true 成立,就表示子串 s[i..L] 是回文,此時(shí)記錄回文長度和起始位置if (dp[i][j] && j - i + 1 > maxLen) {maxLen = j - i + 1;begin = i;}}}return s.substring(begin, begin + maxLen);} }力扣14. 最長公共前綴
編寫一個(gè)函數(shù)來查找字符串?dāng)?shù)組中的最長公共前綴。
如果不存在公共前綴,返回空字符串 “”。
思路:
1701 平均等待時(shí)間
題目:
有一個(gè)餐廳,只有一位廚師。你有一個(gè)顧客數(shù)組 customers ,其中 customers[i] = [arrivali, timei] :
arrivali 是第 i 位顧客到達(dá)的時(shí)間,到達(dá)時(shí)間按 非遞減 順序排列。
timei 是給第 i 位顧客做菜需要的時(shí)間。
當(dāng)一位顧客到達(dá)時(shí),他將他的訂單給廚師,廚師一旦空閑的時(shí)候就開始做這位顧客的菜。每位顧客會(huì)一直等待到廚師完成他的訂單。廚師同時(shí)只能做一個(gè)人的訂單。廚師會(huì)嚴(yán)格按照 訂單給他的順序 做菜。
請你返回所有顧客需要等待的 平均 時(shí)間。與標(biāo)準(zhǔn)答案誤差在 10-5 范圍以內(nèi),都視為正確結(jié)果。
示例 1:
輸入:customers = [[1,2],[2,5],[4,3]] 輸出:5.00000 解釋: 1) 第一位顧客在時(shí)刻 1 到達(dá),廚師拿到他的訂單并在時(shí)刻 1 立馬開始做菜,并在時(shí)刻 3 完成,第一位顧客等待時(shí)間為 3 - 1 = 2 。 2) 第二位顧客在時(shí)刻 2 到達(dá),廚師在時(shí)刻 3 開始為他做菜,并在時(shí)刻 8 完成,第二位顧客等待時(shí)間為 8 - 2 = 6 。 3) 第三位顧客在時(shí)刻 4 到達(dá),廚師在時(shí)刻 8 開始為他做菜,并在時(shí)刻 11 完成,第三位顧客等待時(shí)間為 11 - 4 = 7 。 平均等待時(shí)間為 (2 + 6 + 7) / 3 = 5 。 class Solution {public double averageWaitingTime(int[][] customers) {long endTime = 0;long sum = 0;for (int[] customer : customers) {if (endTime <= customer[0]) {endTime = customer[0];}endTime += customer[1];sum += (endTime - customer[0]);}return sum * 1.0 / customers.length;} }總結(jié)
以上是生活随笔為你收集整理的【华为面试手撕代码】的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: asp企业建站系统 最新推出的 免费下载
- 下一篇: maven中resource配置详解