資料結構,滑動視窗,雙指標,LeetCode,相關題目彙總
class Solution {
public int minSubArrayLen(int target, int[] nums) {
if (nums == null || nums.length < 1){
return 0;
}
int left = 0;
int sum = 0;
int ret = Integer.MAX_VALUE;
for (int right = 0; right < nums.length; right++) {
sum += nums[right];
while (sum >= target){
ret = Math.min(ret , right-left + 1);
sum -= nums[left];
left++;
}
}
return ret == Integer.MAX_VALUE?0:ret;
}
}
74題:
題解:由於二維矩陣固定列的「從上到下」或者固定行的「從左到右」都是升序的,可以從第一行最後一列開始(設為cur變數),分為幾種情況:
cur > target時:說明就在這一行,只要減列就行了
cur == target時:找到了,可以直接返回
cur < target時:說明這一行都不會有這個數,去下一行找,也從下一行的最後一個數開始
依次下去,如果遍歷完了還沒有找到直接返回false
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
if(matrix == null || matrix.length < 1)
return false;
int i = 0;
int j = matrix[0].length-1;
while (i<matrix.length && j >= 0){
int cur = matrix[i][j];
if (cur == target){
return true;
}else if(cur > target){
j--;
}else {
i++;
}
}
return false;
}
}
260題:
方法一:滑動視窗,先排序,然後用一個長度為2的視窗去遍歷整個陣列,相同的直接跳過,不同的就根據情況判斷獲取這兩個值,分為以下幾種情況:
-
下標i跟i-1的值相同,往後跳動兩個格子,如果跳動一個格子,就已經到頂了,說明最後一個就是我們要找的最後一個數。例如:[0,1,1,2]
-
下標i跟i-1的值相同,往後跳動兩個格子,如果跳動一個格子,沒有到頂部,那就continue;
-
下標i跟i-1的值不同,首先說明的是i-1肯定是我們想要的值了,先存起來。然後我們只要往後跳動一個格子,這裡需要判斷一下i是不是到了頂部,如果是的話,最後一個值也是我們需要的,不是的話就繼續滑動。
class Solution {
public int[] singleNumber(int[] nums) {
int[] res = new int[2];
if (nums == null || nums.length < 2){
return res;
}
Arrays.sort(nums);
int index = 0;
for (int i = 1; i < nums.length; i++) {
if (nums[i] == nums[i-1]){
i++;
if (i == nums.length-1){
res[1] = nums[i];
return res;
}
continue;
}
res[index++] = nums[i-1];
if (i == nums.length-1){
res[index++] = nums[i];
}
if (index == 2){
return res;
}
}
return null;
}
}
方法二:用集合,我們可以使用一個雜湊對映統計陣列中每一個元素出現的次數。在統計完成後,我們對雜湊對映進行遍歷,將所有隻出現了一次的數放入答案中。不推薦
class Solution {
public int[] singleNumber(int[] nums) {
int[] res = new int[2];
Map<Integer , Integer> map = new HashMap<>();
for(int num : nums){
map.put(num,map.getOrDefault(num , 0)+1);
}
int index = 0;
for (Integer key : map.keySet()){
if (map.get(key) == 1){
res[index++] = key;
}
}
return res;
}
}
方法三:位運算,異或運算有性質如下:
-
任何數和 0 做異或運算,結果仍然是原來的數,即 a ⊕ 0 = a。
-
任何數和其自身做異或運算,結果是 0,即 a ⊕ a = 0
-
異或運算滿足交換律和結合律,即 a⊕b⊕a=b⊕a⊕a=b⊕(a⊕a)=b⊕0=b。
題解:基於位運算性質
-
先將陣列的所有元素異或得到的一個結果,這個結果為不存在重複的兩個元素異或的結果,因為相同的都已經抵消掉了,同時也不為0,因為這兩個元素是不同的。
-
然後我們需要將來陣列分為兩組,一組包含其中一個我們需要的結果,另外一組包含另外一個我們需要的結果,同時相同的元素必須分到一組,這樣,我們對每一組的所有元素分別進行異或,就可以在每一組中得到一個我們想要的結果,怎麼做呢?
-
lab &= ‑lab得到出 lab最右側的1,因為異或值為1,說明我們需要的兩個值裡面其中一個為0,另外一個為1,這樣才能異或為1
-
然後遍歷,分組,每一組分別異或就可以了
class Solution {
public int[] singleNumber(int[] nums) {
int[] res = new int[2];
int lab = 0;
for(int num : nums){
lab ^= num;
}
lab &= -lab;
for(int num : nums){
if ((num & lab) != 0){
res[0] ^= num;
}else {
res[1] ^= num;
}
}
return res;
}
}
219題:
題解:採用滑動視窗,視窗長度最長為k,若長度不超過k只移動right邊界即可,每次要遍歷視窗內的每一個元素來判斷是否和right相等。如果相等就直接返回,若長度超過k,則需要同時移動left。
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
int left = 0;
for (int right = 0; right < nums.length; right++) {
if (right - left > k ){
left++;
}
for (int i = left; i < right; i++) {
if (nums[i] == nums[right]){
return true;
}
}
}
return false;
}
}
擊敗率不高,想了一下,應該是每次要遍歷增加了時間複雜度,後面改成了HashSet來維持視窗。擊敗了高了一點。
class Solution {
public boolean containsNearbyDuplicate(int[] nums, int k) {
Set<Integer> set = new HashSet<>();
for (int i = 0; i < nums.length; i++) {
if (set.contains(nums[i])){
return true;
}
set.add(nums[i]);
if (set.size() > k){
set.remove(nums[i-k]);
}
}
return false;
}
}
643題:
題解:滑動視窗,用一個大小為k的視窗從陣列的開始,每次往後移動一格,取每次滑動的視窗的和的最大值,最後用這個最大值/k,就是最終我們需要的結果了,這裡需要注意一下的是:我剛開始計算和的時候每次都是從視窗的最左邊算到最右邊,這樣會有很多的重複計算,也會超出時間限制,我們可以利用上一次視窗的和,只需要用這個和減掉nums[left-1],然後加上最新的nums[right]就行了
class Solution {
public double findMaxAverage(int[] nums, int k) {
if (nums == null || nums.length < k){
return 0;
}
int left = 0;
int sum = 0;
for (int i = 0; i < k; i++) {
sum += nums[i];
}
int res = sum;
for (int right = k; right < nums.length; right++) {
if (right - left + 1 > k){
left++;
sum= sum - nums[left-1] + nums[right];
res = Math.max(sum , res);
}
}
return (double) res/k;
}
}
3題:
題解:用兩個指標維持一個視窗,right指標遍歷陣列,對於新來的字元,每次判斷視窗是否包含新的字元,如果沒有包含就更新最大不含有重複字元的 最長子串 的長度,如果包含,就更新left指標,更新至視窗中相同的字元的下一個。直到整個陣列遍歷完成。
class Solution {
public int lengthOfLongestSubstring(String s) {
if (s == null || s.length()<1)
return 0;
int left = 0;
int max = 1;
for (int right = 1; right < s.length(); right++) {
char c = s.charAt(right);
for (int i = left; i < right; i++) {
if (s.charAt(i) == c){
left = i+1;
}
}
max = Math.max(max, right - left + 1);
}
return max;
}
}
1423題:
題解:滑動視窗,每次拿的時候只能從開頭和末尾拿, 而不能從中間拿。也就是說如果把陣列的首尾相連, 串成一個環形, 那麼最終拿掉的k 個元素肯定是連續的, 問題就轉化為求k 個連續元素的最大和。用兩個指標維持一個視窗,從陣列最右邊開始滑動,超過部分就跳到左邊來。
class Solution {
public int maxScore(int[] cardPoints, int k) {
int curMax = 0;
int left = cardPoints.length - k;//視窗的左邊
for (int i = left; i < cardPoints.length; i++) {
curMax += cardPoints[i];
}
int max = curMax;
for (int right = 0; right < k; right++) {//視窗的右邊
curMax = curMax - cardPoints[left];
left++;
left = left >= cardPoints.length?0:left;//維持視窗的左邊
curMax = curMax + cardPoints[right];//視窗右邊有新的元素進來了
max = Math.max(max , curMax);
}
return max;
}
}
76題(困難):
題解:滑動視窗,比較好理解,使用兩個指標left跟right指標, 分別表示視窗的左邊界和右邊界。
-
當視窗內的所有字元不能覆蓋t 的時候, 要擴大視窗, 也就是right往右移。
-
當視窗內的所有字元可以覆蓋t 的時候, 記錄視窗的起始位置以及視窗的長度, 然後縮小視窗 ,left往右移,因為這裡求的是能覆蓋的最小子串,所以這裡還需要判斷是否還能覆蓋t。如果縮小的視窗還能覆蓋t ,儲存長度最小的視窗即可。
重複上面的操作, 直到視窗的右邊不能再移動為止。
class Solution {
public String minWindow(String s, String t) {
if (s.length() < t.length())
return "";
Map<Character , Integer> map = new HashMap<>();
for (char c : t.toCharArray()){
map.put(c , map.getOrDefault(c , 0) + 1);
}
int left = 0;
int len = Integer.MAX_VALUE;
int start = 0;
for (int right = 0; right < s.length(); right++) {
char ch = s.charAt(right);
if (map.containsKey(ch)){
map.put(ch , map.get(ch)-1);
}
int curLen = right - left + 1;
while (curLen >= t.length() && isOk(map)){
//符合條件,更新長度跟開始值
if (curLen < len){
len = curLen;
start = left;
}
//開始去掉左值
char c = s.charAt(left);
if (map.containsKey(c)){
map.put(c , map.getOrDefault(c , 0) + 1);
}
left++;
curLen--;
}
}
return len == Integer.MAX_VALUE?"":s.substring(start , start+len);
}
private boolean isOk(Map<Character , Integer> map){
for (char key : map.keySet()){
if (map.get(key) > 0){
return false;
}
}
return true;
}
}
57題:
題解:雙指標維持一個滑動視窗,從1到target開始遍歷,累計和,如果當前和小於target,視窗右邊滑動,加入新的數到視窗中,如果大於,左邊滑動,直到小於等於target為止,等於的時候收集解就行。
class Solution {
public int[][] findContinuousSequence(int target) {
List<int[]> res = new ArrayList<>();
int sum = 0;
int left = 1;
for (int right = 1; right < target; right++) {
sum += right;
while (sum > target){
sum -= left;
left++;
}
if (sum == target){
int[] arr = new int[right-left+1];
for (int i = left; i <= right; i++) {
arr[i-left] = i;
}
res.add(arr);
}
}
int[][] ret = new int[res.size()][];
for (int i = 0; i < res.size(); i++) {
ret[i] = res.get(i);
}
return ret;
}
}
題解:典型的滑動視窗題目,兩個指標,left跟right分別指向視窗左右邊界,在一個視窗內假如出現次數最多的那個字元出現的次數是a,視窗的長度是b , 只要滿足a + k > b , 我們可以把視窗中的其他字元全部替換為出現次數最多的那個字元,這個時候最大長度就是整個視窗的長度,可以繼續往視窗加入新的元素(因為題目求的是最長重複字元)。相反如果a + k < b , 我們是沒法把視窗內的其他字元全部替換為出現次數最多的那個字元,此時,我們沒有辦法繼續加入新的元素,只能移動left指標,讓視窗減小。如此下去,知道視窗右邊界right沒法移動為止。
class Solution {
public int characterReplacement(String s, int k) {
int[] num = new int[26];
Arrays.fill(num , 0);
int left = 0;
int max = Integer.MIN_VALUE;
int maxLen = Integer.MIN_VALUE;
for (int right = 0; right < s.length(); right++) {
int len = right - left + 1;
int index = s.charAt(right) - 'A';
num[index] = num[index] + 1;
for (int i : num){
max = Math.max(max , i);
}
if (max + k < len){
index = s.charAt(left) - 'A';
num[index] = num[index] - 1;
left++;
}else {
maxLen = Math.max(maxLen , len);
}
}
return maxLen;
}
}
題解:使用兩個指標, 一個從前開始, 一個從後開始, 兩個指標同時往中間走, 如果他們指向的字元不一樣, 那麼這個字串肯定不是迴文字串, 直接返回false即可, 如果這兩個指標相遇了, 直接返回true。
class Solution {
public boolean isPalindrome(String s) {
s = s.toLowerCase();
int left = 0;
int right = s.length()-1;
while (left < right){
while (left<right && !Character.isLetterOrDigit(s.charAt(left))){
left++;
}
while (left<right && !Character.isLetterOrDigit(s.charAt(right))){
right--;
}
if (s.charAt(left) != s.charAt(right)){
return false;
}
left++;
right--;
}
return true;
}
}
題解:找到連結串列的中間節點, 讓他成為樹的根節點, 中間節點前面進行斷開,作為根節點左子樹的所有節點, 中間節點後面的就是根節點右子樹的所有節點, 然後使用遞迴的方式再分別對左右子樹進行相同的操作就可以
class Solution {
public TreeNode sortedListToBST(ListNode head) {
if (head == null)
return null;
if (head.next == null)
return new TreeNode(head.val);
ListNode dummyHead = new ListNode(-1);
dummyHead.next = head;
ListNode slow = dummyHead;
ListNode fast = head;
while (fast != null && fast.next != null){
slow = slow.next;
fast = fast.next.next;
}
ListNode mid = slow.next;
slow.next = null;
TreeNode root = new TreeNode(mid.val);
root.left = sortedListToBST(head);
root.right = sortedListToBST(mid.next);
return root;
}
}
題解:雙指標,首先說一個比較好理解的,根據木桶原理,容量由最短的木板決定,既然如此,我們首先用left指標指向最左邊,right指標指向最右邊,先固定left,從後往前遍歷right,找到第一個比left大的木板,求得面積更新最大容納,然後直接left++,讓right重新從後往前遍歷,不斷更新最大容納,知道left遍歷往整個陣列,同樣的道理,為了防止漏掉,我們也得先固定right,每次讓left從前往後遍歷,不斷更新最大容納就可以得到最大值。
class Solution {
public int maxArea(int[] height) {
int max = Integer.MIN_VALUE;
for (int left = 0; left < height.length; left++) {
for (int right = height.length-1; right >= left; right--) {
if (height[right] >= height[left]){
max = Math.max((right - left) * height[left] , max);
break;
}
}
}
for (int right = height.length-1; right >= 0; right--) {
for (int left = 0; left <= right; left++) {
if (height[left] >= height[right]){
max = Math.max((right - left) * height[right] , max);
break;
}
}
}
return max;
}
}
優化後:同樣雙指標,我們首先用left指標指向最左邊,right指標指向最右邊,求得面積,根據木桶原理,容量由最短的木板決定,所以,我們就讓木板短的那個往裡面靠,嘗試尋找更高的,每次都更新面積,最終可以得到最大容量值。
class Solution {
public int maxArea(int[] height) {
int max = Integer.MIN_VALUE;
int left = 0;
int right = height.length-1;
while (left < right){
if (height[left] < height[right]){
max = Math.max((right - left) * height[left] , max);
left++;
}else {
max = Math.max((right - left) * height[right] , max);
right--;
}
}
return max;
}
}
題解:思路很簡單,程式碼稍微有點多。兩種情況:
-
整個陣列中只有一個最大值,這種情況比較好處理,找到最大值,從這裡分開。
-
陣列開頭到最大值處:用兩個指標,left跟right,移動right,如果right處的值大於left處的值,有兩種情況,一種是連續大於,這種情況left++就行了,因為不可能有積水,如果不是連續大於,那麼就從left到right處求出積水和,直到right抵達最大值處。
-
陣列末尾到最大值出:同樣的道理求出積水和就行。
-
-
整個陣列中有多個最大值
-
這種情況,很好處理,找到其中一個,隨便哪個值+1就可以將來整個陣列變成只有一個最大值了,然後按照第一種情況進行處理就行。
-
public int trap(int[] height) {
if (height.length < 3){
return 0;
}
int leftIndex = 0;
int maxValue = height[0];
for (int i = 1; i < height.length; i++) {
if (height[i] > maxValue){
leftIndex = i;
maxValue = height[i];
}
}
int rightIndex = height.length-1;
maxValue = height[height.length-1];
for (int i = height.length-2; i >= 0; i--) {
if (height[i] > maxValue){
rightIndex = i;
maxValue = height[i];
}
}
if (leftIndex != rightIndex){
height[leftIndex] = height[leftIndex] + 1;
}
int sum = 0;
int left = 0;
for (int right = 1; right < leftIndex+1; right++) {
if (height[right] >= height[left]){
if (right - left > 1){
//求積水
for (int i = left + 1; i < right; i++) {
sum += height[left] - height[i];
}
left = right;
}else {
left++;
}
}
}
left = height.length-1;
for (int right = height.length-2; right >= leftIndex ; right--) {
if (height[right] >= height[left]){
if (left - right > 1){
//求積水
for (int i = left - 1; i > right; i--) {
sum += height[left] - height[i];
}
left = right;
}else {
left--;
}
}
}
return sum;
}