双指针法
这是一种使用两个指针互相配合来存储节点以便于运算的技巧
适用于数组、链表等线性结构,常用思路有碰撞指针、滑动窗口、快慢指针。
对撞指针
leetcode第一题两数之和
class Solution {
public int[] twoSum(int[] nums, int target) {
//双指针法
//采用对撞指针法,时间复杂度降低到 o(n)
int len = nums.length;
int left=0,right=len-1;
int[] res=new int[2];
int[] tmp1=new int[nums.length];
System.arraycopy(nums,0,tmp1,0,nums.length);
//将数组排序
Arrays.sort(nums);
//如何将排序后的数组下标和原有数组下标对应起来????
int m=0,n=0;
for(int i = left,j=right;i< j;){
if(nums[i]+nums[j]==target){
m = i;
n = j;
break;
}else if(nums[i]+nums[j]>target){
//右指针左移动
j--;
}else if(nums[i]+nums[j]<target){
//左指针右移动
i++;
}
}
//两个数不能一样,只能用两个变量了
int i,j;
for ( i=0;i<nums.length;i++) {
if(tmp1[i]==nums[m]){
res[0] = i;
break;
}
}
for ( j=0;i<nums.length;j++) {
// i!=j
if(tmp1[j]==nums[n]&&i!=j){
res[1] = j;
break;
}
}
return res;
}
}
**这道题时间复杂度巨高!!!为什么呢???用到了系统的排序算法,这也暗示我们在使用双指针法的时候,数组一般要有序!**
主要学会这个思想!!!
leetcode881题救生艇
class Solution {
public int numRescueBoats(int[] people, int limit) {
//采用双指针法 对撞指针法
//对数组进行排序
Arrays.sort(people);
//数组长度
int len = people.length;
//计数器及总和
int count=0,sum=0;
//长度为1时
if(len==1){
return 1;
}
//定义指针
int i=0,j=len-1;
while(i<=j){
//题目已经说明了一首船最多在两个人!!!
if(people[i]+people[j]<=limit){
i++;
}
j--;
count++;
}
return count;
}
}
快慢指针
leetcode第141题 环形链表
/**
* Definition for singly-linked list.
* class ListNode {
* int val;
* ListNode next;
* ListNode(int x) {
* val = x;
* next = null;
* }
* }
*/
public class Solution {
public boolean hasCycle(ListNode head) {
ListNode fast ;
ListNode slow = fast = head;
if(head==null){
return false;
}
//while循环里面,快指针如果没有循环,那么链表就终止了
while(fast!=null&&fast.next!=null){
slow = slow.next;
fast = fast.next.next;
if(slow==fast){
return true;
}
}
return false;
}
}
二分查找法
此算法要求,数据结构是顺序存储且关键字有序
leetcode704题二分查找
class Solution {
public int search(int[] nums, int target) {
//暴力解
// for(int i =0; i< nums.length; i++) {
// if(nums[i]==target){
// return i;
// }
// }
// return -1;
// 二分查找法
int left = 0, right = nums.length - 1;
int mid = (left + right) / 2;
while(left<=right){
if(nums[mid]==target){
return mid;
}else if(nums[mid]>target){
right = mid - 1;
}else{
left = mid + 1;
}
mid = (left + right) / 2;
}
return -1;
}
}
leetcode 35 搜索插入位置
class Solution {
public int searchInsert(int[] nums, int target) {
int left = 0, right = nums.length - 1;
while(left <= right) {
int mid = (left + right) / 2;
if(nums[mid] == target) {
return mid;
} else if(nums[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return right+1;
}
}
leetcode162寻找峰值
这一题,我首先想到的是暴力解,讨论各种情况,成功提交!
class Solution {
public int findPeakElement(int[] nums) {
// 暴力解法
// 先讨论数组长度《3的情况
int len = nums.length;
if(len == 1){
return 0;
}else if(len == 2){
if(nums[0]>nums[1]){
return 0;
}
else if(nums[0]<nums[1]){
return 1;
}
}
//讨论数组长度》=3
//定义三个指针
int left = 0;
int mid = 1;
int right = 2;
// 讨论峰值在边界情况
if(nums[0]>nums[1]){
return 0;
}
if(nums[len-1]>nums[len-2]){
return len -1;
}
// 讨论峰值在区间内
while(right < len){
if(nums[mid]>nums[left]&&nums[mid]>nums[right]){
return mid;
}
left++;
mid++;
right++;
}
return 0;
}
}
class Solution {
public int findPeakElement(int[] nums) {
// 二分法
// 使用二分法关键是理解好核心逻辑,一般二分法要排序,但是这样排序后,索引就乱了
// 重新审查题意,发现, nums[-1] = nums[n] = -∞
int len = nums.length;
int left = 0,right = len -1;
int mid = (left + right) / 2;
while(left<right){
// 只要找到一个峰值就可以了!!!
// 说明峰值在右侧
if(nums[mid+1]>nums[mid]){
//右边高,说明在mid右边有峰值,所以mid一定不是
//mid已经不是了,排除掉
left = mid + 1 ;
}
// 说明峰值在左侧
else {
// 左边高,说明左边有峰值,可能mid就是
right = mid ;// mid在下一次查找中还要考虑在内
}
// 这里的代码很贼!,在找峰值的时候,右侧是left=mid+1,左侧是right=mid
// 其实核心就是往右侧靠,因为最左侧,循环到底,相对于num[-1]也是右侧!!!
mid = (left + right) / 2;
}
return left;
}
}
leetcode74 搜索二维矩阵
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
// 这个二维数组是有序的
// 使用二分法
// 获取获取m值,和n值
int m = matrix.length;
int n = matrix[0].length;
// 防止溢出
int left = 0,right = m*n-1;
int mid = left + (right - left) / 2;
// 如何将二维数组的下标索引转换成一维数组索引
while(left <= right){
// 这是问题的核心所在,一维数组索引mid转换二维为 mid/n mid%n
// 二维数组索引x y转换一维为 x*m+y
int value = matrix[mid/n][mid%n];
if(value==target){
return true;
}else if(value>target){
right = mid - 1;
}else{
left = mid + 1;
}
mid = left + (right - left) / 2;
}
return false;
}
}
滑动窗口
数组中的定长问题,想到滑动窗口
leetcode209
class Solution {
public int minSubArrayLen(int s, int[] nums) {
//lo为队列头 li为队列尾,初始min值定义为数组长度+1
int lo = 0, hi = 0, sum = 0, min = nums.length+1;
while (hi < nums.length) {
// 进队+,队尾指针+1,
sum += nums[hi++];
while (sum >= s) {
// 达到条件,每次都寄存最小的min值
min = Math.min(min, hi - lo);
// 这是核心代码,出队-,并且队头指针+1
sum -= nums[lo++];
}
}
return min == nums.length+1 ? 0 : min;
}
//用双指针来实现滑动窗口和队列进出得数据结构
}
leetcode 1456. 定长子串中元音的最大数目
class Solution { public int maxVowels(String s, int k) { // 数组中的定长问题 使用滑动窗口 if(s == null || s.length()==0 || s.length() < k){ return 0; } // 通过hashset查找更高效(相比较for遍历) HashSet<Character> set = new HashSet<>(); set.add('a'); set.add('e'); set.add('i'); set.add('o'); set.add('u'); // 定义零时存储结果集 int res = 0, count = 0; for(int i = 0; i < k; i++){ char temp = s.charAt(i); if(set.contains(temp)){ count++; } } // 判断里面的最大数 res = Math.max(res,count); for(int i = k; i < s.length(); i++){ // 采取一进一出式的判断 char out = s.charAt(i-k); char in = s.charAt(i); if(set.contains(out)){ // 出去的值 -- count--; } if(set.contains(in)){ // 进来的值 count++; } // 每次都要比较最大值 res = Math.max(res,count); } return res; } }
用一个新的函数,时间复杂度更低
class Solution { public int maxVowels(String s, int k) { int cnt = 0; for(int i = 0; i < k; i++){ if(isVowel(s.charAt(i))){ cnt++; } } int res = cnt; for(int i = k; i < s.length(); i++){ if(isVowel(s.charAt(i - k))){ cnt--; } if(isVowel(s.charAt(i))){ cnt++; res = Math.max(res, cnt); } } return res; } private boolean isVowel(char ch) { return ch == 'a' || ch == 'e' || ch == 'i' || ch == 'o' || ch == 'u'; } }
加法模板
只要记住这个公式,不管两个数是列表形式,还是数组形式,都不会写错!
<公式>
当前位 = (A 的当前位 + B 的当前位 + 进位carry) % 10
注意,AB两数都加完后,最后判断一下进位 carry, 进位不为 0 的话加在前面。
<加法模板>
while ( A 没完 || B 没完)
A 的当前位
B 的当前位
和 = A 的当前位 + B 的当前位 + 进位carry
当前位 = 和 % 10;
进位 = 和 / 10;
判断还有进位吗
989:数组形式的整数加法
class Solution {
public List<Integer> addToArrayForm(int[] A, int K) {
int n = A.length;
List<Integer> res = new ArrayList<>(); // 可以用 LinkeList,或者 ArrayList 往后加,最后反转
int i = n - 1, sum = 0, carry = 0;
while (i >= 0 || K != 0) { // 循环条件:两个数有一个没完
int x = i >= 0 ? A[i]: 0;
int y = K != 0 ? K % 10 : 0;
sum = x + y + carry;
carry = sum / 10;
K = K / 10;
i--;
//这里的0 代表的往前添加
res.add(0, sum % 10);
}
if (carry != 0) res.add(0, carry);
return res;
}
}
顺便附上字符串比较的模板。比如这道谷歌高频题:809. 情感丰富的文字
<比较模板>
while( A 没完 && B 没完)
A 的当前字符
B 的当前字符
A 的当前字符长度
B 的当前字符长度
判读符合比较条件吗
判断 A B 都走完了吗
参考例题
leetcode 989 号算法题:数组形式的整数加法
leetcode 66 号算法题:加 1
class Solution {
public int[] plusOne(int[] digits) {
// 又是一题使用加法魔板,但是这个加法魔板有点特殊,主要考察算法思想
// 请不要把数组转换成数字+1,再转换成数组,也不要使用链表装换成数组
// 直接再数组里面操作,然后返回!!!
// int len = digits.length,len2 = 1;
// LinkedList<Integer> res = new LinkedList<Integer>();
// int sum = 0, carry = 0;
// while( len>=0 ){
// int x = len>=0 ? digits[len] : 0;
// int y = len2>0 ? 1 : 0;
// sum = x + y + carry;
// carry = sum / 10;
// res.addFirst(sum % 10);
// len--;
// len2--;
// }
// if(carry!=0){
// res.addFirst(carry);
// }
// return (int[])res.toArray(new int[res.size()]);
// }
// 以上的代码不适用本题,但是思路很相似!
//为何要在数组中直接操作???因为这是一个加1操作,没有任何的变数,直接操作简单的很!!!
//还有就是对问题的分类意识,流程图意思。是解题的关键
int len = digits.length;
for(int i = len -1; i >= 0; i--){
//末尾直接+1
digits[i]++;
//判断是否满10
digits[i] = digits[i] % 10;
// 如果不满10,直接返回
if(digits[i] !=0) return digits;
}
// 如果整个反向遍历都结束了,还没有返回,就要返回 1000000000类型的
// 重新申请一个数组.默认是[0,0,0,0,0,0]类型
int[]temp = new int[digits.length+1];
temp[0] = 1;
return temp;
}
}
leetcode 415 号算法题:字符串相加
leetcode 67 号算法题:二进制求和
leetcode 2 号算法题:两数相加