经典算法题总结:数组常用技巧(双指针,二分查找和位运算)篇 ...

打印 上一主题 下一主题

主题 1005|帖子 1005|积分 3015

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?立即注册

x
双指针

在处置处罚数组和链表相关问题时,双指针技巧是经常用到的,双指针技巧主要分为两类:左右指针快慢指针。所谓左右指针,就是两个指针相向而行或者相背而行;而所谓快慢指针,就是两个指针同向而行,一快一慢。
15. 三数之和(⭐️⭐️)

思路
两数之和 -> 三数之和 -> N 数之和
代码
  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. public class ThreeSumTarget {
  5.     public List<List<Integer>> threeSum(int[] nums) {
  6.         List<List<Integer>> res = new ArrayList<>();
  7.         Arrays.sort(nums);
  8.         for (int i = 0; i < nums.length; i++) {
  9.             if (nums[i] > 0) {
  10.                 return res;
  11.             }
  12.             if (i > 0 && nums[i] == nums[i - 1]) {
  13.                 continue;
  14.             }
  15.             int left = i + 1;
  16.             int right = nums.length - 1;
  17.             while (left < right) {
  18.                 int sum = nums[i] + nums[left] + nums[right];
  19.                 if (sum < 0) {
  20.                     left++;
  21.                 } else if (sum > 0) {
  22.                     right--;
  23.                 } else {
  24.                     res.add(Arrays.asList(nums[i], nums[left], nums[right]));
  25.                     while ((left < right) && (nums[right] == nums[right - 1])) {
  26.                         right--;
  27.                     }
  28.                     while ((left < right) && nums[left] == nums[left + 1]) {
  29.                         left++;
  30.                     }
  31.                     right--;
  32.                     left++
  33.                 }
  34.             }
  35.         }
  36.         return res;
  37.     }
  38. }
复制代码
  1. import java.util.ArrayList;
  2. import java.util.Arrays;
  3. import java.util.List;
  4. public class NSumTarget {
  5.     public List<List<Integer>> threeSum(int[] nums) {
  6.         Arrays.sort(nums);
  7.         return nSumTarget(nums, 3, 0, 0);
  8.     }
  9.     private List<List<Integer>> nSumTarget(int[] nums, int n, int start, int target) {
  10.         int size = nums.length;
  11.         List<List<Integer>> res = new ArrayList<>();
  12.         if (n < 2 || size < n) {
  13.             return res;
  14.         }
  15.         if (n == 2) {
  16.             int low = start;
  17.             int high = size - 1;
  18.             while (low < high) {
  19.                 int sum = nums[low] + nums[high];
  20.                 int left = nums[low];
  21.                 int right = nums[high];
  22.                 if (sum < target) {
  23.                     while (low < high && nums[low] == left) {
  24.                         low++;
  25.                     }
  26.                 } else if (sum > target) {
  27.                     while (low < high && nums[high] == right) {
  28.                         high--;
  29.                     }
  30.                 } else {
  31.                     res.add(new ArrayList<>(Arrays.asList(left, right)));
  32.                     while (low < high && nums[low] == left) {
  33.                         low++;
  34.                     }
  35.                     while (low < high && nums[high] == right) {
  36.                         high--;
  37.                     }
  38.                 }
  39.             }
  40.         } else {
  41.             for (int i = start; i < size; i++) {
  42.                 if (i > start && nums[i] == nums[i - 1]) {
  43.                     continue;
  44.                 }
  45.                 List<List<Integer>> sub = nSumTarget(nums, n - 1, i + 1, target - nums[i]);
  46.                 for (List<Integer> arr : sub) {
  47.                     arr.add(nums[i]);
  48.                     res.add(arr);
  49.                 }
  50.                 while (i < size - 1 && nums[i] == nums[i + 1]) {
  51.                     i++;
  52.                 }
  53.             }
  54.         }
  55.         return res;
  56.     }
  57. }
复制代码
复杂度


  • 时间复杂度:O(N^2)
  • 空间复杂度:O(logN)
5. 最长回文子串

思路
探求回文串的问题焦点头脑是:从中间开始向两边扩散来判定回文串,对于最长回文子串,就是这个意思:
  1. for 0 <= i < len(s):
  2.     找到以 s[i] 为中心的回文串
  3.     更新答案
复制代码
找回文串的关键技巧是传入两个指针 left 和 right 向两边扩散,因为如许实现可以同时处置处罚回文串长度为奇数和偶数的情况。
  1. for 0 <= i < len(s):
  2.     # 找到以 s[i] 为中心的回文串
  3.     palindrome(s, i, i)
  4.     # 找到以 s[i] 和 s[i+1] 为中心的回文串
  5.     palindrome(s, i, i + 1)
  6.     更新答案
复制代码
代码
  1. public class LongestPalindromicSubstring {
  2.     public String longestPalindrome(String s) {
  3.         String res = "";
  4.         for (int i = 0; i < s.length(); i++) {
  5.             String s1 = palindrome(s, i, i); // 奇数情况
  6.             String s2 = palindrome(s, i, i + 1); // 偶数情况
  7.             res = res.length() > s1.length() ? res : s1;
  8.             res = res.length() > s2.length() ? res : s2;
  9.         }
  10.         return res;
  11.     }
  12.     private String palindrome(String s, int left, int right) {
  13.         while (left >= 0 && right < s.length()
  14.                 && s.charAt(left) == s.charAt(right)) {
  15.             left--;
  16.             right++;
  17.         }
  18.         return s.substring(left + 1, right);
  19.     }
  20. }
复制代码
复杂度


  • 时间复杂度:O(N^2)
  • 空间复杂度:O(N)
88. 合并两个有序数组(⭐️⭐️)

思路
代码
  1. public class MergeTwoArray {
  2.     // 双指针
  3.     public void merge1(int[] nums1, int m, int[] nums2, int n) {
  4.         int p1 = 0;
  5.         int p2 = 0;
  6.         int[] sorted = new int[m + n];
  7.         int cur = 0;
  8.         int i = 0;
  9.         while (p1 < m && p2 < n) {
  10.             if (nums1[p1] < nums2[p2]) {
  11.                 sorted[i++] = nums1[p1++];
  12.             } else {
  13.                 sorted[i++] = nums2[p2++];
  14.             }
  15.         }
  16.         while (p1 < m) {
  17.             sorted[i++] = nums1[p1++];
  18.         }
  19.         while (p2 < n) {
  20.             sorted[i++] = nums2[p2++];
  21.         }
  22.         for (i = 0; i < m + n; i++) {
  23.             nums1[i] = sorted[i];
  24.         }
  25.     }
  26.     // 逆向双指针
  27.     public void merge2(int[] nums1, int m, int[] nums2, int n) {
  28.         int i = m - 1;
  29.         int j = n - 1;
  30.         int p = nums1.length - 1;
  31.         while (i >= 0 && j >= 0) {
  32.             if (nums1[i] > nums2[j]) {
  33.                 nums1[p] = nums1[i];
  34.                 i--;
  35.             } else {
  36.                 nums1[p] = nums2[j];
  37.                 j--;
  38.             }
  39.             p--;
  40.         }
  41.         while ( j>= 0) {
  42.             nums1[p] = nums2[j];
  43.             j--;
  44.             p--;
  45.         }
  46.     }
  47. }
复制代码
复杂度


  • 时间复杂度:O(N)
  • 空间复杂度:双指针:O(N),逆向双指针:O(1)
二分查找

  1. int binarySearch(int[] nums, int target) {
  2.     int left = 0, right = nums.length - 1;
  3.     while(left <= right) {
  4.         int mid = left + (right - left) / 2;
  5.         if (nums[mid] < target) {
  6.             left = mid + 1;
  7.         } else if (nums[mid] > target) {
  8.             right = mid - 1;
  9.         } else if(nums[mid] == target) {
  10.             // 直接返回
  11.             return mid;
  12.         }
  13.     }
  14.     // 直接返回
  15.     return -1;
  16. }
复制代码
33. 搜刮旋转排序数组(⭐️⭐️)

思路
代码
  1. public class SearchInRotatedSortedArray {
  2.     /*
  3.         nums = [4,5,6,7,0,1,2]
  4.         例如 target = 5, 目标值在左半段,因此在 [4, 5, 6, 7, inf, inf, inf] 这个有序数组里找就行了
  5.         例如 target = 1, 目标值在右半段,因此在 [-inf, -inf, -inf, -inf, 0, 1, 2] 这个有序数组里找就行了
  6.      */
  7.     public int search(int[] nums, int target) {
  8.         int left = 0;
  9.         int right = nums.length - 1;
  10.         while (left <= right) {
  11.             int mid = left + (right - left) / 2;
  12.             if (nums[mid] == target) {
  13.                 return mid;
  14.             }
  15.             if (target >= nums[0]) {
  16.                 if (nums[mid] < nums[0]) {
  17.                     nums[mid] = Integer.MAX_VALUE;
  18.                 }
  19.             } else {
  20.                 if (nums[mid] >= nums[0]) {
  21.                     nums[mid] = Integer.MIN_VALUE;
  22.                 }
  23.             }
  24.             if (nums[mid] < target) {
  25.                 left = mid + 1;
  26.             } else {
  27.                 right = mid - 1;
  28.             }
  29.         }
  30.         return -1;
  31.     }
  32. }
复制代码
复杂度


  • 时间复杂度:O(logN)
  • 空间复杂度:O(1)
69. x 的平方根(⭐️⭐️)

思路
代码
  1. public class Sqrt {
  2.     public int mySqrt(int x) {
  3.         int left = 0, right = x, res = -1;
  4.         while (left <= right) {
  5.             int mid = left + (right - left) / 2;
  6.             if ((long) mid * mid <= x) {
  7.                 res = mid;
  8.                 left = mid + 1;
  9.             } else {
  10.                 right = mid - 1;
  11.             }
  12.         }
  13.         return res;
  14.     }
  15. }
复制代码
复杂度


  • 时间复杂度:O(N)
  • 空间复杂度:O(1)
240. 搜刮二维矩阵 II(⭐️)
思路


代码
  1. public class SearchMatrix {
  2.     public boolean searchMatrix(int[][] matrix, int target) {
  3.         int m = matrix.length, n = matrix[0].length;
  4.         int i = 0, j = n - 1;
  5.         while (i < m && j >= 0) {
  6.             if (matrix[i][j] == target) {
  7.                 return true;
  8.             }
  9.             if (matrix[i][j] < target) {
  10.                 i++; // 需要大一点,往下移动
  11.             } else {
  12.                 j--; // 需要小一点,往左移动
  13.             }
  14.         }
  15.         return false;
  16.     }
  17. }
复制代码
复杂度


  • 时间复杂度:O(M + N)
  • 空间复杂度:O(1)
34. 在排序数组中查找元素的第一个和最后一个位置(⭐️)
思路
代码
  1. public class SearchRange {
  2.     public int[] searchRange(int[] nums, int target) {
  3.         return new int[]{leftRange(nums, target), rightRange(nums, target)};
  4.     }
  5.     private int leftRange(int[] nums, int target) {
  6.         int left = 0, right = nums.length - 1;
  7.         while (left <= right) {
  8.             int mid = left + (right - left) / 2;
  9.             if (nums[mid] < target) {
  10.                 left = mid + 1;
  11.             } else if (nums[mid] > target) {
  12.                 right = mid - 1;
  13.             } else if (nums[mid] == target) {
  14.                 right = mid - 1;
  15.             }
  16.         }
  17.         if (left < 0 || left >= nums.length) {
  18.             return -1;
  19.         }
  20.         return nums[left] == target ? left : -1;
  21.     }
  22.     private int rightRange(int[] nums, int target) {
  23.         int left = 0, right = nums.length - 1;
  24.         while (left <= right) {
  25.             int mid = left + (right - left) / 2;
  26.             if (nums[mid] < target) {
  27.                 left = mid + 1;
  28.             } else if (nums[mid] > target) {
  29.                 right = mid - 1;
  30.             } else if (nums[mid] == target) {
  31.                 left = mid + 1;
  32.             }
  33.         }
  34.         if (right < 0 || right >= nums.length) {
  35.             return -1;
  36.         }
  37.         return nums[right] == target ? right : -1;
  38.     }
  39. }
复制代码
复杂度


  • 时间复杂度:O(log(N))
  • 空间复杂度:O(1)
34. 在排序数组中查找元素的第一个和最后一个位置(⭐️)
思路
代码
  1. class Solution {
  2.    
  3.     public int[] searchRange(int[] nums, int target) {
  4.         return new int[]{leftBound(nums, target), rightBound(nums, target)};
  5.     }
  6.     private int leftBound(int[] nums, int target) {
  7.         int left = 0, right = nums.length - 1;
  8.         // 搜索区间为 [left, right]
  9.         while (left <= right) {
  10.             int mid = left + (right - left) / 2;
  11.             if (nums[mid] < target) {
  12.                 // 搜索区间变为 [mid+1, right]
  13.                 left = mid + 1;
  14.             } else if (nums[mid] > target) {
  15.                 // 搜索区间变为 [left, mid-1]
  16.                 right = mid - 1;
  17.             } else if (nums[mid] == target) {
  18.                 // 收缩右侧边界
  19.                 right = mid - 1;
  20.             }
  21.         }
  22.         // 检查出界情况
  23.         if (left >= nums.length || nums[left] != target) {
  24.             return -1;
  25.         }
  26.         return left;
  27.     }
  28.     private int rightBound(int[] nums, int target) {
  29.         int left = 0, right = nums.length - 1;
  30.         while (left <= right) {
  31.             int mid = left + (right - left) / 2;
  32.             if (nums[mid] < target) {
  33.                 left = mid + 1;
  34.             } else if (nums[mid] > target) {
  35.                 right = mid - 1;
  36.             } else if (nums[mid] == target) {
  37.                 // 这里改成收缩左侧边界即可
  38.                 left = mid + 1;
  39.             }
  40.         }
  41.         // 这里改为检查 right 越界的情况,见下图
  42.         if (right < 0 || nums[right] != target) {
  43.             return -1;
  44.         }
  45.         return right;
  46.     }
  47.    
  48. }
复制代码
复杂度


  • 时间复杂度:O(log(N))
  • 空间复杂度:O(N)
162. 探求峰值(⭐️)
思路
代码
  1. public class FindPeakElement {
  2.     public int findPeakElement(int[] nums) {
  3.         int left = 0, right = nums.length - 1;
  4.         while (left < right) {
  5.             int mid = left + (right - left) / 2;
  6.             if (nums[mid] > nums[mid + 1]) {
  7.                 right = mid;
  8.             } else {
  9.                 left = mid + 1;
  10.             }
  11.         }
  12.         return left;
  13.     }
  14. }
复制代码
复杂度


  • 时间复杂度:O(logN)
  • 空间复杂度:O(1)
位运算

136. 只出现一次的数字(⭐️)
思路
代码
  1. public class SingleNumber {
  2.    
  3.     public int singleNumber(int[] nums) {
  4.         int res = 0;
  5.         for (int n : nums) {
  6.             res ^= n;
  7.         }
  8.         return res;
  9.     }
  10.    
  11. }
复制代码
复杂度


  • 时间复杂度:O(N)
  • 空间复杂度:O(1)

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

缠丝猫

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表