LeetCode整理----合集一
一:LeetCode[2]? 兩個數相加
? ? ? ? ? 1:需要考慮進位的問題,需要使用一個變量存儲進位標識,每一次都去判斷,而且在一個鏈表判斷完成之后,另外一個鏈表,也是需要單獨考慮進位問題的
? ? ? ? ? 2:在計算結束后,需要再次判斷進位問題,如果有進位,則需要進行處理。
? ? ? ? ? 3:需要維護返回的指針和移動的當前指針,返回的指針確定之后就不再變化,而移動的當前指針會隨著操作而變動,最好每次判斷返回指針是否為null,這樣防止空指針異常。
class Solution {public ListNode addTwoNumbers(ListNode l1, ListNode l2) {//進位標識int flag = 0;ListNode res = null;ListNode next = null;int val1 = 0;int val2 = 0;while(l1 != null || l2 != null){val1 = l1 == null ? 0 : l1.val;val2 = l2 == null ? 0 : l2.val;int i = val1 + val2 + flag;if(l1 != null){l1 = l1.next;}if(l2 != null){l2 = l2.next;}if(i > 9){flag = i/10;i = i%10;}else {flag = 0;}if(res == null){res = new ListNode(i);next = res;}else {next.next = new ListNode(i);next = next.next;}}if(flag == 1){next.next = new ListNode(1);}return res;} }二:LeetCode[19]? ?刪除鏈表的倒數第 n 個節點
? ? ? ? 1.使用雙指針方進行解決,先將一個指針向前移動n位,此時再另兩個指針同時移動,當快的那個指針移動到尾節點,那么慢的指針會移動到倒數第n個節點,此時再進行刪除
? ? ? ? 2.編程時,需要注意的是,當快指針移動到尾部,慢指針所在的位置,要保證慢指針再倒數第n + 1個,這樣才能使用slow.next ?= ?slow.next.next,這一點要特別注意。
class Solution {public ListNode removeNthFromEnd(ListNode head, int n) {ListNode fast = head;ListNode slow = head;for (int i = 0; i < n; i++) {fast = fast.next;}if (fast == null){return slow.next;}while(fast.next != null){fast = fast.next;slow = slow.next;}slow.next = slow.next.next;return head;} }三:LeetCode[21]?合并兩個有序鏈表
? ? ? ? ? ?1.需要維護返回的指針和移動的當前指針,返回的指針確定之后就不再變化,而移動的當前指針會隨著操作而變動,最好每次判斷返回指針是否為null,這樣防止空指針異常。
? ? ? ? ? ?2.在新鏈表的下一個節點確定后,他的下下一個節點要置為null
? ? ? ? ? ?2.清楚指針的維護關系
class Solution {public ListNode mergeTwoLists(ListNode l1, ListNode l2) {if(l1 == null) return l2;if(l2 == null) return l1;ListNode res = null;ListNode current = null;while(l1 != null && l2 != null){if(l1.val > l2.val){if(res == null){res = l2;current = res;}else{current.next = l2;current = current.next;}l2 = l2.next;current.next = null;}else {if(res == null){res = l1;current = res;}else{current.next = l1;current = current.next;}l1 = l1.next;current.next = null;}}if(l1 != null) current.next = l1;if(l2 != null) current.next = l2;return res;} }?
四:LeetCode[206]? 反轉鏈表
? ? ? ? ? 1.需要使用3個指針進行維護,因為在反轉的過程中,是有兩條鏈的
class Solution {public ListNode reverseList(ListNode head) {if(head == null || head.next == null) return head;ListNode current = head;ListNode next = null;ListNode before = null;while(current != null){next = current.next;current.next = before;before = current;current = next;}return before;} }?
五:LeetCode[24]? 兩兩交換鏈表中的節點
? ? ? ? ? ? ? ?1.需要維護返回的節點,每次判斷返回的節點是否為空,為空則進行賦值
? ? ? ? ? ? ? ?2.兩兩交換,這樣的話,指針每次會移動兩位,所以要記錄?next?和?third指針
? ? ? ? ? ? ? ?3.維護一個current節點的前指針,因為在需要在交換之后,將前指針的下一個指向進行修改
class Solution {public ListNode swapPairs(ListNode head) {if(head == null || head.next == null) return head;ListNode res = null;ListNode current = head;ListNode before = null;ListNode next = null;ListNode third = null;//c n t//1 - 2 - 3 - 4 - 5 - 6while(current != null && current.next != null){next = current.next;third = current.next.next;current.next = third;next.next = current;if(res == null){res = next;}else {before.next = next;}before = current;current = third;}return res;} }二刷兩兩交換鏈表
class Solution {public ListNode swapPairs(ListNode head) {if(head == null || head.next == null){return head;}//結果返回的節點ListNode res = null;//前一個節點ListNode per = null;//當前節點ListNode current = null;ListNode next = null;ListNode third = null;// n c t//每次移動兩位 2 -> 1 3 -> 4while(head != null && head.next != null){current = head;next = head.next;third = head.next.next;next.next = current;current.next = null;//確定返回的最終值if(res == null){res = next;}//連接到之前的鏈表中,并維護per指針if(per != null){per.next = next;per = current;}else{per = current;}head = third;}if(head != null){per.next = head;}return res;} }?
六:劍指Offer[06]? 從尾到頭打印鏈表
? ? ? ?1.使用遞歸來解決問題,需要記錄遞歸的深度,用于創建數組
? ? ? ?2.遞歸的結束條件為當前所查節點為null,此時需要創建數組,然后在遞歸返回時,進行賦值
class Solution {public int[] reversePrint(ListNode head) {return rec(head, 0);}//1 -> 3 -> 2public int[] rec(ListNode current, int num){if(current == null){return new int[num];}int[] ints = rec(current.next, ++num);int length = ints.length;//num在rec(current.next, ++num)遞歸時,已經加1了,此時不用再加1了ints[length - num] = current.val;return ints;} }七: LeetCode[328]? 奇偶鏈表
? ? ? ? ? ?1.使用兩個鏈表來進行保存奇偶鏈表,每次循環向前移動兩位指針指向
? ? ? ? ? ?2.需要記錄奇偶鏈表的頭結點和當前訪問的節點,當前節點用于拼接奇偶鏈表的下一個節點,會進行移動
? ? ? ? ? ?3.在最后,奇鏈表的尾部鏈接偶鏈表的頭部即可
class Solution {public ListNode oddEvenList(ListNode head) {if(head == null || head.next == null) return head;ListNode even = null;ListNode odd = null;ListNode evenHead = null;ListNode oddHead = null;while (head != null){if(odd == null){odd = head;oddHead = head;}else {odd.next = head;odd = odd.next;}head = head.next;//需要將下一節點置為null,防止最后一個不設置而出現循環的情況,而且//需要在head = head.next之后,不然節點置為null時,下個節點找不到odd.next = null;if(head == null){break ;}if(even == null){even = head;evenHead = head;}else {even.next = head;even = even.next;}head = head.next;//需要將下一節點置為null,防止最后一個不設置而出現循環的情況,而且//需要在head = head.next之后,不然節點置為null時,下個節點找不到even.next = null;}odd.next = evenHead;return oddHead;} }八: 劍指Offer[22]? 鏈表中倒數第K個節點
? ? ? ? ? 1.雙指針法,先讓快的節點移動k位,再讓兩個指針同時移動,當快的到尾部時,慢的就是倒數第k為
class Solution { // 1->2->3->4->5, 和 k = 2.public ListNode getKthFromEnd(ListNode head, int k) {ListNode slow = head;ListNode fast = head;for (int i = 0; i < k; i++) {fast = fast.next;}while(fast != null){fast = fast.next;slow = slow.next;}return slow;} }九:Leetcode[300]? 最長遞增子序列
給你一個整數數組 nums ,找到其中最長嚴格遞增子序列的長度。?
子序列是由數組派生而來的序列,刪除(或不刪除)數組中的元素而不改變其余元素的順序。例如,[3,6,2,7] 是數組 [0,3,1,6,2,2,7] 的子序列。?
示例 1:?
輸入:nums = [10,9,2,5,3,7,101,18]
輸出:4
解釋:最長遞增子序列是 [2,3,7,101],因此長度為 4 。
題解:?
使用動態規劃進行處理,最長遞增子序列的長度是和前面的子集有關系的,
如求第n個數的最長子序列,那么你就需要求出在 0 - n-1 之間的最長子序列,然后再 + 1,即可求出當前第n個數的最長子序列
在求第n個數的最長子序列, 依次遍歷0 - n-1 之間的最長子序列,而且需要是數字小于第n位時,才能生效
public int lengthOfLIS(int[] nums) {int length = nums.length;//新建數組,保存第n位他的最長子序列int[] dp = new int[length];//存放最終返回的值int res = 1;for (int i = 0; i < length; i++) {if(i == 0){dp[i] = 1;continue;}//依次從0 - n-1 找到比n小的數的最大的最長子序列int max = 0;for(int j = 0; j < i; j++){if(nums[j] < nums[i] && dp[j] > max){max = dp[j];}}dp[i] = max + 1;//進行返回值的更新if(dp[i] > res){res = dp[i];}}return res;}十:劍指Offer[38]? 字符串的排列
輸入一個字符串,打印出該字符串中字符的所有排列。?
你可以以任意順序返回這個字符串數組,但里面不能有重復元素。?
示例:?
輸入:s = "abc"
輸出:["abc","acb","bac","bca","cab","cba"]
題解:
把字符串分為兩部分:一部分是字符串的第一個字符,另一部分是第一個字符以后的所有字符。
第一步是求所有可能出現在第一個位置的字符,即把第一個字符和后面所有字符交換。(for循環、交換操作)
第二步是固定住第一個字符,求后面所有字符的排列。(遞歸)如何求剩余字符的排列:將剩余字符的第一個字符依次和后面的字符進行交換,
而“求后面所有字符的排列”即可按照上面的思路遞歸進行。
遞歸出口:求剩余字符的排列時只剩下一個字符
實現借助一個char[],通過交換元素得到不同的排列,在遞歸返回時將其裝入ArrayList。
如下圖所示,有兩點需要注意:
在每個分支進行完后,要將交換過的元素復位,從而不會影響其他分支。
因為有“Swap A with A”這樣的操作,并且題目指出可能有字符重復,所以分支末尾可能有重復的序列,在加入ArrayList要進行去重判斷,不重復再加入。
以 abc為例,圖解過程如下:
十一:[LeetCode] 165. 比較版本號
題目描述:
比較兩個版本號 version1 和 version2。 如果 version1 > version2 返回 1,如果 version1 < version2 返回 -1, 除此之外返回 0。
你可以假設版本字符串非空,并且只包含數字和 . 字符。
. 字符不代表小數點,而是用于分隔數字序列。
例如,2.5 不是“兩個半”,也不是“差一半到三”,而是第二版中的第五個小版本。
你可以假設版本號的每一級的默認修訂版號為 0。例如,版本號 3.4 的第一級(大版本)和第二級(小版本)修訂號分別為 3 和 4。其第三級和第四級修訂號均為 0。
示例:
示例 1:
輸入: version1 = "0.1", version2 = "1.1" 輸出: -1示例 2:
輸入: version1 = "1.0.1", version2 = "1" 輸出: 1題解:
? ? ? ?先使用逗號進行分割,然后將字符串轉為int型進行比較,需要注意一點的是,兩個字符串使用逗號分割之后的長度可能不同,這個需要進行一下特殊處理。
class Solution {public int compareVersion(String version1, String version2) {String[] split1 = version1.split("\\.");String[] split2 = version2.split("\\.");int length1 = split1.length;int length2 = split2.length;//找到最大的長度int maxLength = length1 > length2 ? length1 : length2;for (int i = 0; i < maxLength; i++) {String splitSub1 = null;String splitSub2 = null;//判斷是否達到最大,防止數組越界if(i < length1){splitSub1 = split1[i];}if(i < length2){splitSub2 = split2[i];}//都在的情況if(splitSub1 != null && splitSub2 != null){//都轉為int然后再進行比較Integer integer1 = Integer.valueOf(splitSub1);Integer integer2 = Integer.valueOf(splitSub2);if(integer1 > integer2){return 1;}else if(integer1 < integer2){return -1;}}else if(splitSub1 == null){Integer integer2 = Integer.valueOf(splitSub2);if(integer2 != 0){return -1;}}else {Integer integer1 = Integer.valueOf(splitSub1);if(integer1 != 0){return 1;}}}return 0;} }十二:[LeetCode] 11. 盛最多水的容器
題目描述:
給定?n?個非負整數?a1,a2,...,an,每個數代表坐標中的一個點 (i,?ai) 。在坐標內畫?n?條垂直線,垂直線?i?的兩個端點分別為 (i,?ai) 和 (i, 0)。找出其中的兩條線,使得它們與?x?軸共同構成的容器可以容納最多的水。
說明:你不能傾斜容器,且 n 的值至少為 2。
圖中垂直線代表輸入數組 [1,8,6,2,5,4,8,3,7]。在此情況下,容器能夠容納水(表示為藍色部分)的最大值為 49。
示例:
輸入: [1,8,6,2,5,4,8,3,7] 輸出: 49題解:
? ?設立兩個指針,一個從頭一個從尾,相向而行遍歷數組,每次舍棄較短邊
(1)計算面積最大值的初值,該初值以數組中的第一個元素和最后一個元素構成兩邊。
(2)設置首尾兩個指針,首指針i指向數組中的第一個元素,尾指針j指向數組中的最后一個元素。
(3)當首指針i小于尾指針j時,一直循環計算其面積。若計算所得的當前面積大于(1)步驟中所計算得的面積最大值,則更新最大值。每一次循環都舍棄索引i和索引j中較短的那一條邊。
為什么每一次循環舍棄索引i和索引j中較短的那一條邊,我們最終得到的結果就會是最大的面積值呢?
反證法:
假設我們現在遍歷到了height數組中第i和第j個元素,且height[i] < height[j],如果我們的面積最大值中取了第i個元素,那么構成我們的面積最大值的兩個元素一定是i和j,因為j繼續減小的話長方形的寬肯定一直在減小,而其高最多只能是height[i],不可能比height[i]更大,因此我們在繼續遍歷的過程中,繼續保持i不變而減小j是沒有意義的。我們可以直接舍棄i,從i + 1開始去繼續遍歷。
由于整個過程只遍歷了一次數組,因此時間復雜度為O(n),其中n為數組height的長度。而使用空間就是幾個變量,故空間復雜度是O(1)。
class Solution {public int maxArea(int[] height) {if(height == null || height.length == 1){return 0;}int length = height.length;int right = length-1;int left = 0;int with = length-1;int res = 0;while (left < right){int minHeight = height[left] > height[right] ? height[right] : height[left];int temp = minHeight * with;if(temp > res){res = temp;}if(height[left] > height[right]){right--;}else {left++;}with--;}return res;} }十三:[LeetCode] 26. 刪除排序數組中重復項
給定一個排序數組,你需要在原地刪除重復出現的元素,使得每個元素只出現一次,返回移除后數組的新長度。
不要使用額外的數組空間,你必須在原地修改輸入數組并在使用 O(1) 額外空間的條件下完成。
示例?1:
給定數組 nums = [1,1,2],
函數應該返回新的長度 2, 并且原數組 nums 的前兩個元素被修改為 1, 2。
你不需要考慮數組中超出新長度后面的元素。
示例?2:
給定 nums = [0,0,1,1,1,2,2,3,3,4],
函數應該返回新的長度 5, 并且原數組 nums 的前五個元素被修改為 0, 1, 2, 3, 4。
你不需要考慮數組中超出新長度后面的元素。
說明:
為什么返回數值是整數,但輸出的答案是數組呢?
請注意,輸入數組是以“引用”方式傳遞的,這意味著在函數里修改輸入數組對于調用者是可見的。
你可以想象內部操作如下:
// nums 是以“引用”方式傳遞的。也就是說,不對實參做任何拷貝
int len = removeDuplicates(nums);
// 在函數里修改輸入數組對于調用者是可見的。
// 根據你的函數返回的長度, 它會打印出數組中該長度范圍內的所有元素。
for (int i = 0; i < len; i++) {
? ? print(nums[i]);
}
題解:
官方題解:雙指針法
數組完成排序后,我們可以放置兩個指針i 和j,其中i 是慢指針,而j 是快指針。只要 nums[i] = nums[j],我們就增加 j 以跳過重復項。
當我們遇到 nums[j] != nums[i],跳過重復項的運行已經結束,因此我們必須把它(nums[j])的值復制到 nums[i + 1]。然后遞增 i,接著我們將再次重復相同的過程,直到 j 到達數組的末尾為止。
復雜度分析
時間復雜度:O(n),假設數組的長度是 n,那么 i 和 j 分別最多遍歷 n 步。
空間復雜度:O(1)
十四:[LeetCode] 104. 二叉樹最大深度
給定一個二叉樹,找出其最大深度。?
二叉樹的深度為根節點到最遠葉子節點的最長路徑上的節點數。?
說明: 葉子節點是指沒有子節點的節點。?
示例:?
給定二叉樹 [3,9,20,null,null,15,7],?
? ? 3
? / \
?9 ?20
? ?/ ?\
? 15 ? 7?
返回它的最大深度 3 。?
Related Topics 樹 深度優先搜索 遞歸
題解:
? ? ? ?直接使用遞歸判斷就行,每次判斷一個子樹的左節點和右節點哪個深度最大,并再次基礎上加1即可。
class Solution {public int maxDepth(TreeNode root) {if(root == null) return 0;//進行左右節點的遞歸工作int left = maxDepth(root.left, 1);int right = maxDepth(root.right, 1);return Math.max(left, right);}private int maxDepth(TreeNode root, int depth){if(root == null){return depth;}return Math.max( maxDepth(root.left, depth + 1), maxDepth(root.right, depth + 1));} }十五:[LeetCode] 104. 重建二叉樹
輸入某二叉樹的前序遍歷和中序遍歷的結果,請重建該二叉樹。假設輸入的前序遍歷和中序遍歷的結果中都不含重復的數字。?
例如,給出?
前序遍歷 preorder =?[3,9,20,15,7]
中序遍歷 inorder = [9,3,15,20,7]?
?返回如下的二叉樹:?
? ? ?3
? ?/ \
? 9 ?20
? ? / ?\
? ?15 ? 7?
?限制:?
?0 <= 節點個數 <= 5000?
題解:
遞歸思想!
? ? ?使用前序和中序遍歷可以確認唯一的二叉樹,通過前序可以得知根節點為1,然后通過中序可以得知左子樹為{4, 7, 2},右子樹為{5, 3, 8, 6}。接下來通過遞歸思想,{4, 7, 2}中4為根節點,再繼續分左右子樹,對{5, 3, 8, 6}也做同樣操作,直至遍歷結束。
在二叉樹的前序遍歷序列中,第一個數字總是樹的根結點的值。但在中序遍歷序列中,根結點的值在序列的中間,左子樹的結點的值位于根結點的值的左邊,而右子樹的結點的值位于根結點的值的右邊。因此我們需要掃描中序遍歷序列,才能找到根結點的值。
前序遍歷序列的第一個數字1就是根結點的值。掃描中序遍歷序列,就能確定根結點的值的位置。根據中序遍歷特點,在根結點的值1前面的3個數字都是左子樹結點的值,位于1后面的數字都是右子樹結點的值。
在二叉樹的前序遍歷和中序遍歷的序列中確定根結點的值、左子樹結點的值和右子樹結點的值的步驟如下圖所示:
自己的理解:
? ? ? ? ?1.遞歸時,要注意,是先構建好子樹,然后再構建好外層的樹,我開始就是從外部開始構建的,也就是說是先構建好根節點,然后將跟根節點傳遞給子節點,并且判斷當前是左字樹,還是右子樹,然后再進行其他操作,這樣的話,違背了規則的原則,也是一個錯誤的思想,子樹的構建,不應該牽連到父類是什么樣子的。
? ? ? ? 2.在子樹遞歸時,需要時刻注意前根遍歷的開始下標和結束下標,這個一定要判斷好,而對于中根遍歷的的話,因為根在中間,正好可以平均分配,而前根就不一樣了,這是能做出提的關鍵。
class Solution {public TreeNode buildTree(int[] preorder, int[] inorder) {if(preorder == null || inorder == null){return null;}return recurseBuildTree(preorder, 0, preorder.length-1, inorder, 0, inorder.length-1);}public TreeNode recurseBuildTree(int[] preorder, int perLeft, int perRight, int[] inorder, int inLeft, int inRight){if(perLeft > perRight || inLeft > inRight){return null;}TreeNode root = new TreeNode(preorder[perLeft]);for(int i=inLeft; i<=inRight; i++){if(inorder[i] == root.val){//(i-inLeft)為左子樹的個數,在加上perLeft,那么就是左子樹的結束root.left = recurseBuildTree(preorder, perLeft+1, i-inLeft+perLeft, inorder, inLeft, i-1);//(i-inLeft)為左子樹的個數,在加上perLeft,那么就是左子樹的結束,再加1,那么就是右子樹的開始root.right = recurseBuildTree(preorder, i-inLeft+perLeft+1, perRight, inorder, i+1, inRight);}}return root;} }十六:[LeetCode] 70. 爬樓梯
假設你正在爬樓梯。需要 n 階你才能到達樓頂。?
?每次你可以爬 1 或 2 個臺階。你有多少種不同的方法可以爬到樓頂呢??
?注意:給定 n 是一個正整數。?
?
示例 1:?
輸入: 2
輸出: 2
解釋: 有兩種方法可以爬到樓頂。
1. ?1 階 + 1 階
2. ?2 階?
示例 2:?
輸入: 3
輸出: 3
?
Related Topics 動態規劃
題解:
使用動態規劃,你爬第n階樓梯時,可以從n-1上來,也可以從n-2階樓梯上來,所以你上樓梯的方法是在n-1和n-2的總和
class Solution {public int climbStairs(int n) {if(n==1) return 1;if(n==2) return 2;int[] dp = new int[n];dp[0] = 1;dp[1] = 2;for(int i=2; i<n;i++){dp[i] = dp[i-1] + dp[i-2];}return dp[n-1];} }十七:[LeetCode] 189. 旋轉數組
給定一個數組,將數組中的元素向右移動 k 個位置,其中 k 是非負數。?
?進階:?
?盡可能想出更多的解決方案,至少有三種不同的方法可以解決這個問題。?
?你可以使用空間復雜度為 O(1) 的 原地 算法解決這個問題嗎??
示例 1:
輸入: nums = [1,2,3,4,5,6,7], k = 3
輸出: [5,6,7,1,2,3,4]
解釋:
向右旋轉 1 步: [7,1,2,3,4,5,6]
向右旋轉 2 步: [6,7,1,2,3,4,5]
向右旋轉 3 步: [5,6,7,1,2,3,4]
?
?示例 2:?
?
輸入:nums = [-1,-100,3,99], k = 2
輸出:[3,99,-1,-100]
解釋:?
向右旋轉 1 步: [99,-1,-100,3]
向右旋轉 2 步: [3,99,-1,-100]?
題解:
? ? ? 解法一:可以增加一個數組保存新移動的數據,這個簡單
? ? ? 解法二:先進行整體的反轉,然后根據右移數,進行兩次局部的反轉
?
class Solution {public void rotate(int[] nums, int k) {//先進行整體的反轉,再進行局部的反轉,int length = nums.length;k %= length;//整體反轉 如nums = [1,2,3,4,5,6,7], k = 3recurseArray(nums, 0, length-1); //[7, 6, 5, 4, 3, 2, 1]//局部的反轉recurseArray(nums, 0, k-1); //[5, 6, 7, 4, 3, 2, 1]recurseArray(nums, k, length-1); //[5, 6, 7, 1, 2, 3, 4]}private void recurseArray(int[] nums, int i, int j){//兩兩比較if(i>j){return;}while(i<j){swap(nums, i, j);i++;j--;}}private void swap(int[] nums, int i, int j){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;} }十八:[LeetCode] 15. 三數之和
給你一個包含 n 個整數的數組?nums,判斷?nums?中是否存在三個元素 a,b,c ,使得?a + b + c = 0 ?請你找出所有和為 0 且不重復的三元組。
注意:答案中不可以包含重復的三元組。
示例 1:
輸入:nums = [-1,0,1,2,-1,-4]
輸出:[[-1,-1,2],[-1,0,1]]
示例 2:
輸入:nums = []
輸出:[]
示例 3:
輸入:nums = [0]
輸出:[]
?
提示:
0 <= nums.length <= 3000
-105 <= nums[i] <= 105
題解:
? ? ? 可以先對數組進行排序,使用Arrays.sort進行排序,然后數組是從到大進行排序的了,然后使用?a+b = -c,先固定一個數,然后使用雙指針法進行數組的查找,寫代碼時,要注意不能出現重復的數。
需要注意的點:
? ? ? 1.每次都需要進行判斷,當前下標值和上一個下標值是否一樣,一樣則跳過
? ? ? 2.數組只需要遍歷到當前下標值>=0就行,如果都大于1,顯然不滿足條件
class Solution {public List<List<Integer>> threeSum(int[] nums) {if(nums == null){return null;}Arrays.sort(nums);List<List<Integer>> list = new ArrayList<>();for(int i=0; i<nums.length-2 && nums[i]<=0; i++){//去除重復的元素if(i!=0 && nums[i]==nums[i-1]){continue;}int left = i+1;int right = nums.length-1;twoSum(list, nums[i], left, right, nums);}return list;}private void twoSum(List<List<Integer>> list, int target, int left, int right, int[] nums){int pre = nums[left] - 1; //保證第一次pre不能和nums[left]相同,之后會用這個判斷是否發生了重復while(left < right){if(pre == nums[left]){left++;continue;}int temp = 0 - (nums[left] + nums[right]);if(target == temp){pre = nums[left]; //這個要相同之后,再進行一個判斷,防止重復List<Integer> subList = new ArrayList<>();subList.add(nums[left]);subList.add(nums[right]);subList.add(target);list.add(subList);left++;right--; //這步不能忘}else if(target > temp){right--;}else{left++;}}} }十九:[LeetCode] 3. 無重復字符的最長子串
給定一個字符串,請你找出其中不含有重復字符的?最長子串?的長度。
示例?1:
輸入: s = "abcabcbb"
輸出: 3?
解釋: 因為無重復字符的最長子串是 "abc",所以其長度為 3。
示例 2:
輸入: s = "bbbbb"
輸出: 1
解釋: 因為無重復字符的最長子串是 "b",所以其長度為 1。
示例 3:
輸入: s = "pwwkew"
輸出: 3
解釋: 因為無重復字符的最長子串是?"wke",所以其長度為 3。
?? ? 請注意,你的答案必須是 子串 的長度,"pwke"?是一個子序列,不是子串。
示例 4:
輸入: s = ""
輸出: 0
提示:
0 <= s.length <= 5 * 104
s?由英文字母、數字、符號和空格組成
題解:
? ? ? 使用滑動窗口進行求解,使用map進行存儲已經存在的元素,字符 ->? 數組下標 的形勢進行保存,循環時,每次都判斷,當前字符是否出現過,如果出現過,那么就要更新滑動窗口的開始位置,開始位置的判斷是本題的解題關鍵
解題注意點:
? ? ? ?1.需要到了HashMap的containsKey方法,這個要會寫
? ? ? ?2.在map.get(c)時,可能字符串在map中,但是出現的位置在滑動窗口的左邊,此時這個值是不能算是在滑動窗口出現過的,這個需要特別注意,比如: kababkc,當判斷數組下標為5, 字符'k'時,此時的滑動窗口的開始下標為3, 此時k已經在map中,下標為1 ,所以對于下標開始的判斷很關鍵
class Solution {public int lengthOfLongestSubstring(String s) {int n = s.length();Map<Character, Integer> map = new HashMap<>(); int res = 0;int start = 0;for (int j = 0; j < n; j++) {//取出該元素值char c = s.charAt(j);if(map.containsKey(c)){//得到字符串的下一個位置,這個位置可能會成功滑動窗口的開始位置int start2 = map.get(c)+1;//取出一個最大的當做開始,為什么需要這么做,因為這個map是沒有刪除的,也就是說map中存放的元素,有可能在//滑動窗口的左邊,這時取得值都已經作廢了//比如: kababkc,當判斷數組下標為5, 字符'k'時,此時的滑動窗口的開始下標為3, 此時k已經在map中,下標為1//所以對于下標開始的判斷很關鍵start = Math.max(start, start2);}map.put(c, j);//進行值的更新if(res < (j-start+1)){res = j-start+1;}}return res;} }二十:[LeetCode] 128. 最長連續序列
給定一個未排序的整數數組?nums?,找出數字連續的最長序列(不要求序列元素在原數組中連續)的長度。
進階:你可以設計并實現時間復雜度為?O(n)?的解決方案嗎?
示例 1:
輸入:nums = [100,4,200,1,3,2] 輸出:4 解釋:最長數字連續序列是 [1, 2, 3, 4]。它的長度為 4。示例 2:
輸入:nums = [0,3,7,2,5,8,4,6,0,1] 輸出:9提示:
- 0 <= nums.length <= 104
- -109?<= nums[i] <= 109
題解:
? ? ? ?使用哈希表進行解決,依次遍歷整個數組進行查找,而且在查找時,我們只從開始位置進行查詢,也就是說,當我們從 2查找時,1不能存在,1存在的話,就會出現重復,導致我們查找的出現重復。
注意點:
? ? ? ?1.使用HashSet將整個數組進行保存,去重
? ? ? ? 2.依次遍歷數組的每個元素,如果該元素減1,不存放于數組中,那么它就是一個開始位置,我們從這個依次+1,去HashSet中進行查找。
class Solution {public int longestConsecutive(int[] nums) {//先將數都放到HashSet中HashSet<Integer> set = new HashSet<>();for(int i=0;i<nums.length;i++){set.add(nums[i]);}int res = 0;for(int i=0; i<nums.length; i++){int num = nums[i];//set中不包含它的一個數的前驅,說明它就是開始,那么以它為開始節點,進行搜索if(!set.contains(num-1)){//以當前節點為開始,進行一個搜索,如果set中也存放num++,那么長度+1int current = 0;while(set.contains(num+1)){num++;current++;}res = Math.max(res, current+1);}}return res;} }二十一:[LeetCode] 17. 電話號碼的字母組合
給定一個僅包含數字?2-9?的字符串,返回所有它能表示的字母組合。答案可以按 任意順序 返回。
給出數字到字母的映射如下(與電話按鍵相同)。注意 1 不對應任何字母。
示例 1:
輸入:digits = "23"
輸出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
輸入:digits = ""
輸出:[]
示例 3:
輸入:digits = "2"
輸出:["a","b","c"]
?
提示:
0 <= digits.length <= 4
digits[i] 是范圍 ['2', '9'] 的一個數字。
題解:
? ? ? 全排列的一般都是使用遞歸,此時需要注意的是怎么遞歸,結束條件是什么
注意:
? ? ? ?1.對于每個數字對應的字符進行映射,使用Map<Character, String>進行存儲
? ? ? ?2.獲取到數組對應的String數組,因為每個數字對應一個數組,所以是String,這里依次從String的第一個開始,然后進行遞歸
? ? ? ?3.使用StringBuilder進行保存,可以進行恢復
class Solution {public List<String> letterCombinations(String digits) {if(digits == null || digits.length() == 0){return new ArrayList<>();}Map<Character, String> map = new HashMap<>();map.put('2',"abc");map.put('3',"def");map.put('4',"ghi");map.put('5',"jkl");map.put('6',"mno");map.put('7',"pqrs");map.put('8',"tuv");map.put('9',"wxyz");String[] strArr = new String[digits.length()];for(int i=0; i<digits.length(); i++){char c = digits.charAt(i);strArr[i] = map.get(c);}List<String> list = new ArrayList<>();//從第0個開始,每次遞歸調用recurseCombinations(strArr, 0, list, new StringBuilder());return list;}private void recurseCombinations(String[] strArr, int index, List<String> list, StringBuilder sb){//當遍歷完成后,轉為String,并放到集合中if(strArr.length == index){list.add(sb.toString());return;}char[] chars = strArr[index].toCharArray();//依次遍歷當前的每一個字符for(int i=0; i<chars.length; i++){sb.append(chars[i]);recurseCombinations(strArr, index+1, list, sb);sb.deleteCharAt(index); //進行元素的復原操作}} }二十二:[LeetCode] 22. 括號生成
數字 n?代表生成括號的對數,請你設計一個函數,用于能夠生成所有可能的并且 有效的 括號組合。
示例 1:
輸入:n = 3
輸出:["((()))","(()())","(())()","()(())","()()()"]
示例 2:
輸入:n = 1
輸出:["()"]
提示:
1 <= n <= 8
題解:
? ? ? ?使用遞歸解決,并且關于字符串使用StringBuilder,?使用兩個變量都為n,?一個代表左括號的剩余數量left,一個代表右括號的剩余數量right,? 初始都為n,?當左括號、右括號剩余數量都為0
時,進行生成字符串。
? ? ? 遞歸的返回條件為left<0? ||??right< 0 ||?left >?right,其中對于left >?right表示的是左括號剩余的大于右括號,如())這樣的就不滿足條件,直接返回。
class Solution {public List<String> generateParenthesis(int n) {List<String> list = new ArrayList<>();StringBuilder sb = new StringBuilder();recurseGenerate(n, n, sb, list);return list;}private void recurseGenerate(int left, int right, StringBuilder sb, List<String> list){if(left > right || left < 0 || right < 0){ //left > right,表示如果左括號剩余的多,說明本次是違規的操作,比如))()((,這樣的輸出不合法return;}if(left == 0 && right == 0){list.add(sb.toString());}recurseGenerate(left-1, right, sb.append("("), list);sb.deleteCharAt(sb.length()-1);recurseGenerate(left, right-1, sb.append(")"), list);sb.deleteCharAt(sb.length()-1);} }二十三:[LeetCode] 200. 島嶼數量
給你一個由?'1'(陸地)和 '0'(水)組成的的二維網格,請你計算網格中島嶼的數量。
島嶼總是被水包圍,并且每座島嶼只能由水平方向和/或豎直方向上相鄰的陸地連接形成。
此外,你可以假設該網格的四條邊均被水包圍。
示例 1:
輸入:grid = [
? ["1","1","1","1","0"],
? ["1","1","0","1","0"],
? ["1","1","0","0","0"],
? ["0","0","0","0","0"]
]
輸出:1
示例 2:
輸入:grid = [
? ["1","1","0","0","0"],
? ["1","1","0","0","0"],
? ["0","0","1","0","0"],
? ["0","0","0","1","1"]
]
輸出:3
?
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j] 的值為 '0' 或 '1'
題解:
? ? ? ?使用DFS進行求解,如果當前遍歷的坐標符合要求,則將坐標位置置為0
注意點:
? ? ? ?1.遞歸的結束條件
class Solution {public int numIslands(char[][] grid) {int n = grid.length;int m = grid[0].length;int res = 0;for(int i=0; i<n; i++){for(int j=0; j<m; j++){if(grid[i][j] == '1'){res++;recurseNumIslands(grid, i, j);}}}return res;}private void recurseNumIslands(char[][]grid, int i, int j){//遞歸結束條件要清楚,特別是grid[i][j] == '0',不然就會出錯if(i<0 || i>= grid.length || j<0 || j>=grid[0].length || grid[i][j] == '0'){return;}//設置為0grid[i][j] = '0';//進行遞歸recurseNumIslands(grid, i+1 ,j);recurseNumIslands(grid, i-1 , j);recurseNumIslands(grid, i , j+1);recurseNumIslands(grid, i , j-1);} }二十四:[LeetCode] 543. 二叉樹的直徑
給定一棵二叉樹,你需要計算它的直徑長度。一棵二叉樹的直徑長度是任意兩個結點路徑長度中的最大值。這條路徑可能穿過也可能不穿過根結點。?
示例 :?
給定二叉樹?
? ? ? ? ? ?1
? ? ? ? ?/ \
? ? ? ? 2 ? 3
? ? ? ?/ \ ? ??
? ? ? 4 ? 5 ? ?
?返回 3, 它的長度是路徑 [4,2,1,3] 或者 [5,2,1,3]。?
?注意:兩結點之間的路徑長度是以它們之間邊的數目表示。?
?Related Topics 樹
題解:
使用遞歸 + 動態規劃解決問題
?遞歸調用每個節點,而且每個節點保存兩個值,一個是當前節點的最大深度, 一個是以當前節點為一個子樹的內部的最大深度,
?使用數組進行保存,int[2]{maxDiameterWithCurrentRootNode, maxDeep }
?當前節點的使用current[],左節點使用leftTemps[],右節點使用RightTemps[],求當前節點時,左右節點的值均已求出
?current[0] = max(leftTemps[0], ?RightTemps[0], ?leftTemps[1] + RightTemps[1] + 1), 其中leftTemps[1] + RightTemps[1] + 1為已當前節點為根的最大直徑
?current[1] = max(leftTemps[1] , ?RightTemps[1]) + 1
二十五:[LeetCode] 146. LRU緩存機制
運用你所掌握的數據結構,設計和實現一個 LRU (最近最少使用) 緩存機制 。?
?
實現 LRUCache 類:?
?
LRUCache(int capacity) 以正整數作為容量 capacity 初始化 LRU 緩存?
int get(int key) 如果關鍵字 key 存在于緩存中,則返回關鍵字的值,否則返回 -1 。?
void put(int key, int value) 如果關鍵字已經存在,則變更其數據值;如果關鍵字不存在,則插入該組「關鍵字-值」。當緩存容量達到上
限時,它應該在寫入新數據之前刪除最久未使用的數據值,從而為新的數據值留出空間。?
進階:你是否可以在 O(1) 時間復雜度內完成這兩種操作??
示例:?
輸入
["LRUCache", "put", "put", "get", "put", "get", "put", "get", "get", "get"]
[[2], [1, 1], [2, 2], [1], [3, 3], [2], [4, 4], [1], [3], [4]]
輸出
[null, null, null, 1, null, -1, null, -1, 3, 4]
解釋
LRUCache lRUCache = new LRUCache(2);
lRUCache.put(1, 1); ?緩存是 {1=1}
lRUCache.put(2, 2); ?緩存是 {1=1, 2=2}
lRUCache.get(1); ? ? 返回 1
lRUCache.put(3, 3); ?該操作會使得關鍵字 2 作廢,緩存是 {1=1, 3=3}
lRUCache.get(2); ? ? 返回 -1 (未找到)
lRUCache.put(4, 4); ?該操作會使得關鍵字 1 作廢,緩存是 {4=4, 3=3}
lRUCache.get(1); ? ? 返回 -1 (未找到)
lRUCache.get(3); ? ? 返回 3
lRUCache.get(4); ? ? 返回 4
?提示:?
?1 <= capacity <= 3000?
?0 <= key <= 3000?
?0 <= value <= 104?
?最多調用 3 * 104 次 get 和 put?
題解:
? ? ? 使用LinkedHashMap可以很輕松的實現,但是你這樣的話,沒有起到自己設計的目的,只是自己調用了API,所以,這里使用HashMap進行設計。
注意點:
? ? ? 1.LRU需要使用鏈表結構進行保存,就像LinkedHashMap一樣,構造虛擬節點內部類,保存到HashMap的value中
? ? ? 2.HashMap中value保存的是虛擬的鏈表節點,如果value直接存放值的話,工作量太大
? ? ? 3.構建出虛擬的頭結點和尾節點,只是用來指明頭尾的位置,這樣的不用考慮空指針異常的問題,這個能想到的話,做題很快的
? ? ? 4.明白LUR的含義,在get時,將訪問的當前節點移動到頭部去,當put時,先查看是否存在,如不存在,則創建虛擬節點進行包裝后,放入,再判斷是否超過最大容量,超過時,則需要刪除。
class LRUCache {class CacheLinked{int key;int val;CacheLinked next;CacheLinked pre;public CacheLinked(){}public CacheLinked(int key, int val){this.key = key;this.val = val;}}//size存放當前元素數, capacity存放最大元素數private int size;private int capacity;private CacheLinked head;private CacheLinked tail;Map<Integer, CacheLinked> map = new HashMap<>();public LRUCache(int capacity) {this.capacity = capacity;//構建頭尾的虛擬節點,不存放任何元素,只是為了操作簡單,不用進行空指針的判斷,如果不構建//虛擬節點有你受的了,光空指針的判斷就很麻煩head = new CacheLinked();tail = new CacheLinked();head.next = tail;tail.pre = head;}public int get(int key) {CacheLinked cache = map.get(key);if(cache == null){return -1;}//更新lru,移除元素,并添加到首部moveCache(cache);addHead(cache);return cache.val;}public void put(int key, int value) {CacheLinked cache = map.get(key);//分兩種情況,緩存已存在,和緩存不存在,if(cache == null){CacheLinked newCache = new CacheLinked(key, value);addHead(newCache);//判斷容量大小,如果超過了,那么就將最就的那個從hashmap中刪除size++;if(size > capacity){CacheLinked oldTail = moveTail();//移除元素map.remove(oldTail.key);}map.put(key, newCache);}else{//更新值cache.val = value;//更新lru,移除元素,并添加到首部moveCache(cache);addHead(cache);}}private void addHead(CacheLinked cache){CacheLinked oldHead = head.next;head.next = cache;cache.next = oldHead;oldHead.pre = cache;cache.pre = head;}private void moveCache(CacheLinked cache){//因為有虛擬節點的存在,根本不用判斷空指針的問題cache.pre.next = cache.next;cache.next.pre = cache.pre;//釋放元素,防止內存遺漏cache.next = null;cache.pre = null;}//移除尾部元素,并將尾部元素進行返回private CacheLinked moveTail(){CacheLinked oldTail = tail.pre;CacheLinked newTail = oldTail.pre;newTail.next = tail;tail.pre = newTail;oldTail.next = null;oldTail.pre = null;return oldTail;} }二十六:[LeetCode] 337. 打家劫舍
在上次打劫完一條街道之后和一圈房屋后,小偷又發現了一個新的可行竊的地區。這個地區只有一個入口,我們稱之為“根”。 除了“根”之外,每棟房子有且只有一個“父“
房子與之相連。一番偵察之后,聰明的小偷意識到“這個地方的所有房屋的排列類似于一棵二叉樹”。 如果兩個直接相連的房子在同一天晚上被打劫,房屋將自動報警。?
計算在不觸動警報的情況下,小偷一晚能夠盜取的最高金額。?
示例 1:?
輸入: [3,2,3,null,3,null,1]
? ? ?3
? ? / \
? ?2 ? 3
? ? \ ? \?
? ? ?3 ? 1
輸出: 7?
解釋:?小偷一晚能夠盜取的最高金額 = 3 + 3 + 1 = 7.?
示例 2:?
輸入: [3,4,5,1,3,null,1]
?? ? 3
? ? / \
? ?4 ? 5
? / \ ? \?
?1 ? 3 ? 1
輸出: 9
解釋:?小偷一晚能夠盜取的最高金額?= 4 + 5 = 9.
?
Related Topics 樹 深度優先搜索
題解:
????? 使用遞歸 + 動態規劃進行求解,因為每個節點都可以選也可以不選,如果當前節點選擇的話,他的兩個子節點都不能選擇了,所以,我們可以使用數組保存當前節點的兩個狀態,即[0]不選當前節點的最大值, [1]選擇了當前節點的最大值
注意:
? ? ? 1.對于選擇當前節點還好,直接左右節點不選 + 當前節點的value
? ? ? ?而對于不選當前節點,那么左節點和右節點都可以選或者是不選,所以,我們要查出最大的左節點和最大的右節點,如下面這樣情況
? ? ? ? ?3
? ? ? /? ? ?\
? ? ?4? ? ? ?5
? ?/ ? \? ? ? ? \
1 ?10000 ? ?1
? ?當遍歷到根節點3時,我們看根據是不選4這個節點,他的整體會更大一點
二十七:[LeetCode] 101. 對稱二叉樹
給定一個二叉樹,檢查它是否是鏡像對稱的。?
例如,二叉樹 [1,2,2,3,4,4,3] 是對稱的。?
? ? ?1
? ?/ \
? 2 ? 2
?/ \ / \
3 ?4 4 ?3
但是下面這個 [1,2,2,null,3,null,3] 則不是鏡像對稱的:?
? ? ?1
? ?/ \
? 2 ? 2
? ?\ ? \
? ?3 ? ?3
進階:?
你可以運用遞歸和迭代兩種方法解決這個問題嗎??
Related Topics 樹 深度優先搜索 廣度優先搜索
題解:
?? ??? ?首先分析下這個對稱二叉樹,也就是一個二叉樹中間對稱。所以我們可以使用遞歸的思想,首先以根節點以及其左右子樹,左子樹的左子樹和右子樹的右子樹相同,左子樹的右子樹和右子樹的左子樹相同。兩個條件都要符合,所以我們第一個傳根節點的左子樹和右子樹,先判斷左右子樹根結點的比較。然后分辨對左子樹的左子樹和右子樹的右子樹。左子樹的右子樹和右子樹的左子樹進行判斷。只有兩個條件都滿足則返回的是true,一層一層遞歸進入,則可以得到結果。
注意:
?? ??? ?1.主要遞歸進行判斷,如果值不相同的話,那么也不是一個鏡像二叉樹
?? ??? ?2.只要有一個返回為false,那么整個結果就會返回false
二十八:[LeetCode] 98. 驗證二叉搜索樹
給定一個二叉樹,判斷其是否是一個有效的二叉搜索樹。?
假設一個二叉搜索樹具有如下特征:?
節點的左子樹只包含小于當前節點的數。?
節點的右子樹只包含大于當前節點的數。?
所有左子樹和右子樹自身必須也是二叉搜索樹。?
?
示例 1:?
輸入:
? ? 2
? ?/ \
? 1 ? 3
輸出: true
?
示例 2:?
輸入:
? ? 5
? ?/ \
? 1 ? 4
? ? ? / \
? ? 3 ? 6
輸出: false
解釋: 輸入為: [5,1,4,null,null,3,6]。
根節點的值為 5 ,但是其右子節點值為 4 。
題解:
?? ??? ?先使用中序遍歷,二叉搜索樹的話,對于中序遍歷來說,整個數組是有序的,所以可以依次判斷數組的后一個是否比前一個數組大,如果小的話,則不滿足。
?? ??? ?
?
二十九:[LeetCode] 64. 最小路徑和
給定一個包含非負整數的 m x n 網格 grid ,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和為最小。?
說明:每次只能向下或者向右移動一步。?
示例 1:?
輸入:grid = [[1,3,1]
? ? ? ? ? ? ,[1,5,1],
? ? ? ? ? ? ?[4,2,1]]
輸出:7
解釋:因為路徑 1→3→1→1→1 的總和最小。
?
示例 2:?
輸入:grid = [[1,2,3],[4,5,6]]
輸出:12
提示:?
遞歸的話,結束條件時什么
到達右下角,則結束, ?結束之后返回值,因為只能向左或者是向右,則進行比較,左右做小值
?m == grid.length?
?n == grid[i].length?
?1 <= m, n <= 200?
?0 <= grid[i][j] <= 100?
?
?Related Topics 數組 動態規劃
?
?題解:
兩種解法: ?
?? ??? ?一種是遞歸,會超時.....................,每次遞歸判斷向左向右的最小值,并進行求值
?? ??? ?二種是動態規劃,第dp[i][j]代表走到i,j所走的最小路徑,那么這個最小路徑和和dp[i-1][j],dp[i][j-1]有關系的,
?? ??? ? ? ? 具體的最小值為dp[i][j] = Math.max(dp[i-1][j], ?dp[i][j-1]) + grid[i][j];
注意:
? ? ? ? 對于臨界點的判斷,因為i=0的話,你再i-1就會出錯了,這個要特別注意
class Solution {public int minPathSum(int[][] grid) {if(grid == null){return 0;}int n = grid.length;int m = grid[0].length;int[][] dp = new int[n][m];for(int i=0; i<n; i++){for(int j=0; j<m; j++){if(i == 0 && j == 0){dp[i][j] = grid[i][j];}else if(i == 0) {//進入這里,i==0,那么j肯定不為0dp[i][j] = dp[i][j-1]+grid[i][j];}else if(j == 0){//進入這里,j==0,那么i肯定不為0dp[i][j] = dp[i-1][j]+grid[i][j];}else{dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + grid[i][j];}}}return dp[n-1][m-1];}// return recurseReachMinPathSum(grid, 0, 0);//這里解法會超時的,不用看public int recurseReachMinPathSum(int[][]grid, int i, int j){//結束條件if(i==grid.length-1 && j==grid[0].length-1){return grid[i][j];}int down = Integer.MAX_VALUE;int right = Integer.MAX_VALUE;//條件判斷,剪紙if(i < grid.length-1){down = recurseReachMinPathSum(grid, i+1, j);}if(j < grid[0].length-1){right = recurseReachMinPathSum(grid, i, j+1);}return Math.min(down, right) + grid[i][j];} }三十:[LeetCode] 115. 最小棧
設計一個支持 push ,pop ,top 操作,并能在常數時間內檢索到最小元素的棧。?
push(x) —— 將元素 x 推入棧中。?
pop() —— 刪除棧頂的元素。?
top() —— 獲取棧頂元素。?
getMin() —— 檢索棧中的最小元素。?
?
示例:?
輸入:
["MinStack","push","push","push","getMin","pop","top","getMin"]
[[], ? ? ? ?[-2], ? [0], ? [-3], ? ?[], ? ?[], ? [], ? ? []]
輸出:
[null, ? ? ? null, ? null, ?null, ? -3, ? ?null, ?0, ? ? -2]
解釋:
MinStack minStack = new MinStack();
minStack.push(-2);
minStack.push(0);
minStack.push(-3);
minStack.getMin(); ? --> 返回 -3.
minStack.pop();
minStack.top(); ? ? ?--> 返回 0.
minStack.getMin(); ? --> 返回 -2.
提示:?
pop、top 和 getMin 操作總是在 非空棧 上調用。?
Related Topics 棧 設計
題解:
? ? ? ?使用額外的一個棧保存當前最小的數,每當一個元素進棧時,都會在兩個棧中各存放一個元素,最小棧中存放的是和之前的進行比較,看哪個小,就將小的放入棧中,具體官方題解有動態圖,這個看一下:https://leetcode-cn.com/problems/min-stack/solution/zui-xiao-zhan-by-leetcode-solution/
?
class MinStack {private LinkedList<Integer> stack;private LinkedList<Integer> minStack;/** initialize your data structure here. */public MinStack() {stack = new LinkedList<>();minStack = new LinkedList<>();}public void push(int x) {//如果為空,則直接存放進去if(stack.size() == 0){minStack.addFirst(x);}else{//不為空,取出最小棧中的頭部,并進行判斷大小,而且每次都會將比較//之后最小的,放入到最小棧中int temp = minStack.getFirst();if(temp > x){minStack.addFirst(x);}else{minStack.addFirst(temp);}}stack.addFirst(x);}public void pop() {stack.removeFirst();minStack.removeFirst();}public int top() {return stack.getFirst();}public int getMin() {return minStack.getFirst();} }三十一:[LeetCode] 5. 最長回文子串
給你一個字符串 s,找到 s 中最長的回文子串。
?
示例 1:
輸入:s = "babad"
輸出:"bab"
解釋:"aba" 同樣是符合題意的答案。
示例 2:
輸入:s = "cbbd"
輸出:"bb"
示例 3:
輸入:s = "a"
輸出:"a"
示例 4:
輸入:s = "ac"
輸出:"a"
?
提示:
1 <= s.length <= 1000
s 僅由數字和英文字母(大寫和/或小寫)組成
題解:
??? ?
先給出官方題解,然后寫一下自己的理解,需要注意和深刻理解的點:
?? ??? ?1.求dp[i][j]是否是回文串時,需要dp[i+1][j-1]的狀態已知,而對于dp[i+1][j-1]的狀態也是需要它上面的狀態才行,所以,在進行求值時,我們需要先根據內部的才能求出外部的,在
求解時,需要根據長度進行求取,先求出所有的回文子串長度為1的,然后再求出長度為2的,再求為3的
?? ??? ?2.所有循環時,不是對i和j進行循環,外層對長度進行循環,內層對開始位置i進行循環,根據i和長度來確定j的位置,這樣進行求解
三十二:[LeetCode] 647. 回文子串
給定一個字符串,你的任務是計算這個字符串中有多少個回文子串。?
具有不同開始位置或結束位置的子串,即使是由相同的字符組成,也會被視作不同的子串。?
示例 1:?
輸入:"abc"
輸出:3
解釋:三個回文子串: "a", "b", "c"
示例 2:?
輸入:"aaa"
輸出:6
解釋:6個回文子串: "a", "a", "a", "aa", "aa", "aaa"?
提示:?
輸入的字符串長度不會超過 1000 。?
Related Topics 字符串 動態規劃
題解:
?? ??? ?需要使用動態規劃,思路和leetcode第5題思路是一樣的,主要的還是對于動態轉移方式的判斷,和條件
需要注意的點:
?? ??? ?1.dp[i][j]表示從數組下標i到數組下標j是否是一個回文串,它的轉移方程為dp[i][j] = (arr[i] == arr[j] && dp[i+1][j-1] == true)
?? ??? ?2.求解時,根據長度進行求解,讓回文字串不斷增大,先從長度為1 到長度為length進行判斷
三十三:[LeetCode] 33. 搜索旋轉排序數組
整數數組 nums 按升序排列,數組中的值 互不相同 。?
在傳遞給函數之前,nums 在預先未知的某個下標 k(0 <= k < nums.length)上進行了 旋轉,使數組變為 [nums[k], nums[
k+1], ..., nums[n-1], nums[0], nums[1], ..., nums[k-1]](下標 從 0 開始 計數)。例如, [0,1,2
,4,5,6,7] 在下標 3 處經旋轉后可能變為 [4,5,6,7,0,1,2] 。?
給你 旋轉后 的數組 nums 和一個整數 target ,如果 nums 中存在這個目標值 target ,則返回它的索引,否則返回 -1 。?
示例 1:?
輸入:nums = [4,5,6,7,0,1,2], target = 0
輸出:4
示例 2:?
輸入:nums = [4,5,6,7,0,1,2], target = 3
輸出:-1?
示例 3:?
輸入:nums = [1], target = 0
輸出:-1
提示:?
1 <= nums.length <= 5000?
-10^4 <= nums[i] <= 10^4?
nums 中的每個值都 獨一無二?
nums 肯定會在某個點上旋轉?
-10^4 <= target <= 10^4?
進階:你可以設計一個時間復雜度為 O(log n) 的解決方案嗎??
Related Topics 數組 二分查找
題解:
? ? ? ?使用二分進行查詢,當將數組一分為二時,肯定有一份是從小到大排序的,這個是可以肯定的,因為此題有個前提-------數組中的值互不相同,所以,我們解題的關鍵是找到哪一半是升序的,然后依次判斷這個target是否在這個升序排列中,如果不存在,那么就在另一份了,所以此題也是二分的思想
class Solution {public int search(int[] nums, int target) {if(nums == null || nums.length == 0){return -1;}if(nums.length == 1){return nums[0] == target ? 0 : -1;}int left = 0;int right = nums.length-1;int mid = 0;while(left <= right){mid = (left + right)/2;if(nums[mid] == target){return mid;}//從有序的一方進行判斷if(nums[left] <= nums[mid] ){//左邊有序if(nums[left] <= target && nums[mid] > target){right = mid - 1;}else{left = mid + 1;}}else{if(nums[mid] < target && nums[right] >= target){left = mid + 1;}else{right = mid - 1;}}}return -1;} }三十四:[LeetCode] 56. 合并區間
以數組 intervals 表示若干個區間的集合,其中單個區間為 intervals[i] = [starti, endi] 。請你合并所有重疊的區間,并返
回一個不重疊的區間數組,該數組需恰好覆蓋輸入中的所有區間。?
示例 1:?
輸入:intervals = [[1,3],[2,6],[8,10],[15,18]]
輸出:[[1,6],[8,10],[15,18]]
解釋:區間 [1,3] 和 [2,6] 重疊, 將它們合并為 [1,6].
?
示例 2:?
輸入:intervals = [[1,4],[4,5]]
輸出:[[1,5]]
解釋:區間 [1,4] 和 [4,5] 可被視為重疊區間。?
提示:?
1 <= intervals.length <= 104?
intervals[i].length == 2?
0 <= starti <= endi <= 104?
?
Related Topics 排序 數組
題解:
? ? ? 進行合并的區域,需要先進行排序,因為,可能給的數組不是從小到達排序的,排序時,按照開始節點進行排序,例如給的用例為[[2,3],[4,5],[6,7],[8,9],[1,10]],此時就需要先進行排序了,再排序之后我們就可以發現規律了,如
[[1,10],[2,3],[4,5],[6,7],[8,9]],此時如果需要進行排序,那么肯定是當前遍歷元素的開始比前一個元素的結束要小,此時就要進行合并,而且,再查找一個最大值,作為結束。
class Solution {public int[][] merge(int[][] intervals) {if(intervals == null || intervals.length == 1){return intervals;}ArrayList<int[]> list = new ArrayList<>();//從小到大進行排序,按照起始節點進行排序Arrays.sort(intervals, (a,b) -> a[0] - b[0]);list.add(intervals[0]);for(int i=1; i<intervals.length; i++){//取出最后一個放入的元素,進行判斷int[] temp = list.get(list.size() - 1);//如果當前遍歷的開始,比列表中最后一個要小,也就是列表中最后一個更大,//此時需要加入到其中,并且更新列表中最后一個數的結尾,查看哪個數更大一點if(intervals[i][0] <= temp[1]){temp[1] = Math.max(intervals[i][1], temp[1]);}else{list.add(intervals[i]);}}return list.toArray(new int[][]{});} }三十五:[LeetCode] 160. 相交鏈表
編寫一個程序,找到兩個單鏈表相交的起始節點。
例如,下面的兩個鏈表:
A: ? ? ? ? ?a1 → a2
? ? ? ? ? ? ? ? ? ?↘
? ? ? ? ? ? ? ? ? ? ?c1 → c2 → c3
? ? ? ? ? ? ? ? ? ?↗ ? ? ? ? ? ?
B: ? ? b1 → b2 → b3
在節點 c1 開始相交。
注意:
如果兩個鏈表沒有交點,返回?null.
在返回結果后,兩個鏈表仍須保持原有的結構。
可假定整個鏈表結構中沒有循環。
程序盡量滿足 O(n) 時間復雜度,且僅用 O(1) 內存。
思路:如果沒有?O(n) 時間復雜度,且僅用 O(1) 內存的條件,這個題很容器寫,但是因為對于時間復雜度和空間復雜度有要求,所以,我們應該再做思考,我們知道,如果兩個鏈表相交,從相交點開始,以后的元素都是相同的,我們設A鏈表中,不重復的為listA,設B鏈表中,不重復的為listB,相同的那部分為AequalB。
????????那么就有listA+AequalB+listB=listB+AequalB+listA,當然這也是顯然易見的,所以我們就可以找到交點了,
????????例如,對于A鏈表,我們a1 → a2 → c1→c2 → c3→b1 → b2 → b3→c1
???????????????????對于B鏈表,我們b1 → b2 → b3→c1 → c2 → c3→a1 → a2→c1
?????????可以發現,我們從A鏈表開始,當A鏈表遍歷完后,再從B鏈表頭節點找,?對于B,我們從B鏈表開始,當B鏈表遍歷完后,再從A鏈表頭節點找,這樣會在相交的地方,兩值相等。
?
注意:
? ? ? ?分為兩種情況,如果A和B鏈一樣長,那么第一次遍歷時,就直接a == b == null了,如果A和B鏈不一樣長,那么第一次遍歷時,就直接a == null時,b一定不是null,那么就需要進行a = headB; b = headA的判斷
public class Solution {public ListNode getIntersectionNode(ListNode headA, ListNode headB) {if(headA==null || headB==null){return null;}//分為兩種情況: 如果A和B鏈一樣長,那么第一次遍歷時,就直接a == b == null了// 如果A和B鏈不一樣長,那么第一次遍歷時,就直接a == null時,b一定不是null,那么就需要進行a = headB; b = headA的判斷//a -> b//c -> d, 如果A和B鏈一樣長,那么第一次遍歷時,就直接a == b == null了,所以直接退出,如果A,B鏈不想等,那么的話,才需要進行二次遍歷//如a -> b//c -> d -> e, 只有A,B鏈不同時為null時,才會走掃描第二次,也就是a = headB; b = headA;才有用ListNode a = headA;ListNode b = headB;while(a != b){if(a == null){a = headB;}else{a = a.next;}if(b == null){b = headA;}else{b = b.next;}}return a;} }三十六:[LeetCode] 31. 下一個排列
實現獲取 下一個排列 的函數,算法需要將給定數字序列重新排列成字典序中下一個更大的排列。?
如果不存在下一個更大的排列,則將數字重新排列成最小的排列(即升序排列)。?
必須 原地 修改,只允許使用額外常數空間。?
示例 1:?
輸入:nums = [1,2,3]
輸出:[1,3,2]
?
示例 2:?
輸入:nums = [3,2,1]
輸出:[1,2,3]
?
示例 3: ?
輸入:nums = [1,1,5]
輸出:[1,5,1]
?
示例 4:?
輸入:nums = [1]
輸出:[1]
?
提示:?
1 <= nums.length <= 100?
0 <= nums[i] <= 100?
Related Topics 數組
題解:
? ? ? ? ?首先你要知道什么是字典樹,其次你要很清楚怎么根據一個字典樹,找到他的下一個排列的字典樹。這兩點清楚的話,這題并不難,主要的話,還是要理解字典樹,詳細的可以參考這篇:https://blog.csdn.net/qq_33594380/article/details/82377923。
1.字典序值
? ? ? ? ?字典序值就是當前序列在字典序中的排列位置。那給定一個排列,我們應該如何求出它的字典序值呢?
為了求排列對應的序號,只要該序列到第一個序列即1,2,3…n?所需要移動的次數。
移動原則是a[i] 從大到小移動,對于每一個數字a[i],若i前面比a[i] 小的個數正好是a[i] - 1 個,則這個數不需要向后移動以達到目標序列,否則i 后面必然有a[i] - t - 1 (此處t 的值為a[i] 之前比a[i] 小的數字的個數)個比a[i] 小的數,只要把a[i] 移動到比自己小的數后面才能使得移動中的序列正向目標前進。
因此只要求出每個數的移動次數,然后相加就是該序列的位置。即每個數到正確位置需要移動的次數為
—— 這一段引自?https://blog.csdn.net/hello_tomorrow_111/article/details/78696294?并略作優化
啥意思,有點模糊,舉個栗子來解釋一下。我們就以第一點中提到的[1, 2, 3] 的字典序為例,我現在想知道321 在序列中的位置應該怎么計算呢?
(1)找到該序列的第一個序列,即123
(2)從序列左邊開始,查找每個值應該移動的位數。
首先來看3,與第一個序列相比,3 之前本來應該有兩個元素12,但是現在它前面沒有比它小的元素,所以它要移動3 - 1 - 0 (a[i] - t - 1),即兩位到達正序位置。
接著來看2,2 之前應該是有一個元素1,但是它前面也沒有比它小的元素,所以它要向后移動2 - 1 - 0,即一位來達到正序。
對于1 來說,它是排列中最小的元素,它之前不應該有元素,而事實也是如此,所以它不需要移動。
將3 和2 移動的次數相加就是321 在字典序中的字典序值。那么3 和2 分別需要移動多少次才能移動兩位或者一位呢,以3 為例來看一下:
321 中的3 往后移動兩位需要經歷以下流程(回退):231、213、132、123。
2. 下一個序列
字典序值說完了,說說下一個序列,下一個序列求解的思想非常簡單:
(1)從右向左找到第一個左臨小于右臨的位置,左臨的位置標記為i
(2)從右向左找到第一個大于a[i] 的數值,位置為j
(3)交換a[i] 與a[j] 的值
(4)將i 右邊的部分排成正序
如果看這個流程看不懂的話,建議去看Leetcode 上的動圖,保證看幾遍就懂:
https://leetcode-cn.com/problems/next-permutation/solution/
Say sth more (簡稱SM): 為什么要這么做呢,提供一個栗子結合理解,假設我們要求15499 + 1 的和,這個應該怎么算呢?
解法是:從右向左找到第一個不為9 的數值,將該數值加1,然后該數值以后所有的9 都變成0。此例可以與求解下一個序列的過程結合理解。
class Solution {public void nextPermutation(int[] nums) {if(nums == null || nums.length == 1){return;}int length = nums.length;int ans = -1;//1 2 3 8 7 6 5 4// i-1 i//1, 2, 3// ans//第一步.從后向前找第一個,a[i] > a[i-1]的數for(int i=length-1; i>0; i--){if(nums[i] > nums[i-1]){ans = i-1;//break語句不能忘記,不然就錯了......break;}}if(ans == -1){//此時不存在下一個更大的排列,則將數字重新排列成最小的排列reverseArray(nums, 0, length-1);}else{for(int i=length-1; i>=ans; i--){if(nums[i] > nums[ans]){//第二步,元素的交換swapNum(nums, i, ans);//第三步,指定的數據反轉reverseArray(nums, ans+1, length-1);break;}}}}//交換元素private void swapNum(int[] nums, int i, int j){int temp = nums[i];nums[i] = nums[j];nums[j] = temp;}//反轉指定下標范圍內的元素private void reverseArray(int[] nums, int l, int r){while(l<r){swapNum(nums, l, r);l++;r--;}} }?
總結
以上是生活随笔為你收集整理的LeetCode整理----合集一的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 消息队列MQ快速入门
- 下一篇: USB TYPE-C PIN定义