【双指针】经典数组双指针题LeetCode

27. 移除元素 简单

题目链接

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。
示例 2:

输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。


思路:双指针

  • right指针不是val就赋值给left,right++,left++
  • right指针是val就跳过,right++

代码:

class Solution {
    public int removeElement(int[] nums, int val) {
        int left = 0,right = 0;
        while(right<nums.length){
            if(nums[right]!=val){
                nums[left] = nums[right];
                left++;
            }
            right++;
        }
        return left;
    }
}

283. 移动零 简单🔥

题目链接

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]
示例 2:

输入: nums = [0]
输出: [0]


思路: 前后双指针,将每一个不是0的元素赋值给前面的指针,对未重新赋值的部分赋值0。
代码:

class Solution {
    public void moveZeroes(int[] nums) {
        int left = 0;
        int right = 0;
        // 依次将right指针指向的非0元素赋值给left指针
        while(right<nums.length){
            if(nums[right]!=0){
                nums[left] =  nums[right];
                left++;
            }
            right++;
        }
        // 从 left 开始 赋值0
        for(int i = left; i < nums.length;i++){
            nums[i] = 0;
        }

    }
}

167. 两数之和 II - 输入有序数组 中等

题目链接

给你一个下标从 1 开始的整数数组 numbers ,该数组已按 非递减顺序排列 ,请你从数组中找出满足相加之和等于目标数 target 的两个数。如果设这两个数分别是 numbers[index1] 和 numbers[index2] ,则 1 <= index1 < index2 <= numbers.length 。

以长度为 2 的整数数组 [index1, index2] 的形式返回这两个整数的下标 index1 和 index2。

你可以假设每个输入 只对应唯一的答案 ,而且你 不可以 重复使用相同的元素。

你所设计的解决方案必须只使用常量级的额外空间。

示例 1:

输入:numbers = [2,7,11,15], target = 9
输出:[1,2]
解释:2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。
示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]
解释:2 与 4 之和等于目标数 6 。因此 index1 = 1, index2 = 3 。返回 [1, 3] 。
示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]
解释:-1 与 0 之和等于目标数 -1 。因此 index1 = 1, index2 = 2 。返回 [1, 2] 。

思路:在已排序数组(非递减)的前提条件下,Left指针指向第一个元素,Right指针指向最后一个元素。当两者大于目标值时,移动Left指针(left++),小于目标值时,移动Right指针(right–),直到找到目标答案。

代码:

class Solution {
    public int[] twoSum(int[] numbers, int target) {

        int left = 0;
        int right = numbers.length-1;
        
        while(left!=right){
            if(numbers[left]+numbers[right]==target){ // 等于目标值
                return new int[]{left+1,right+1};
            }
            if(numbers[left]+numbers[right]>target){ // 大于目标值,通过right-- 以缩小两者的和
                right--;
            }else{                                  //  小于目标值,通过left++ 以扩大两者的和
                left++;
            }
        }
        return new int[]{-1,-1};
    }
}

11. 盛最多水的容器 中等🔥

题目链接

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明:你不能倾斜容器。
在这里插入图片描述


思路:该题首先想到的思路就是暴力搜索,两个For循环寻找最大值,但是时间复杂度过高。我们知道计算机两条线中间容器所盛水的容量取决于两条线中的短边,为了计算容量的最大值,我们只需要更改短边即可。因此,采用左右双指针法,每次移动短边所在的指针,以寻找最大容量。

代码:

class Solution {
    public int maxArea(int[] height) {
        // 左右指针
        int left = 0;
        int right = height.length - 1;

        int result = 0;

        while(left<right){
            // 计算当前盛水量
            int currentResult = Math.min(height[left],height[right]) * (right-left);

            // 更新结果
            result = Math.max(result,currentResult);

            // 左右指针所在的两条线谁矮谁移动,以求最大值
            if(height[left]<height[right]){
                left++;
            }else{
                right--;
            }
        }
        return result;
    }
}

15. 三数之和 中等(N数之和)中等🔥

题目链接

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != j、i != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意:答案中不可以包含重复的三元组。

示例 1:

输入:nums = [-1,0,1,2,-1,-4]
输出:[[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1] 和 [-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。
示例 2:

输入:nums = [0,1,1]
输出:[]
解释:唯一可能的三元组和不为 0 。
示例 3:

输入:nums = [0,0,0]
输出:[[0,0,0]]
解释:唯一可能的三元组和为 0 。

思路:

  • 先将数组排序,将三数之和化简为二数之和问题;先固定一个值,寻找另外两个值。
  • 二数问题依旧采用双指针法解决,但需要考虑答案重复问题,每次移动指针,比如left和left++所在的值一样,则会重复寻找,因此继续++,直到不同。
  • 在数组中,从第一个值开始依次选取一个值进行固定,在其后面的数组中继续寻找另外两个值。(不用再寻找固定值前面的值,否则依旧会重复查找)。
  • 可以将三数之和问题扩展为N数之和问题,将目标和为0改为target。通过递归法将问题不断缩小。
  • 5数之和->就是把每一个4数之和的答案加上固定的那个值,就是5数之和的答案。

代码(N数之和):

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums); // 先排序 
        return nSumTarget(nums,0,0,3); // 3数之和等于0
    }

    /**
        nSumTarget:可以解决N sum问题
        参数:
        - nums:数组
        - target:目标值
        - start:只在[start,nums.length-1]区间寻找
        - needNum:需要几个数,两数之和就是2,三数之和就是3
     */
    public List<List<Integer>> nSumTarget(int[] nums,int target,int start,int needNum){
        List<List<Integer>> res = new ArrayList<>();
        
        if(needNum<2 || nums.length<needNum) return res;

        
        if( needNum == 2){ // 同理 二数之和

                int left = start,right = nums.length-1;
                while(left<right){
                    int leftnum = nums[left];
                    int rightnum = nums[right];
                    if(leftnum+rightnum == target){
                        res.add(new ArrayList<Integer>(Arrays.asList(leftnum,rightnum)));
                        while(left<right && nums[right]==rightnum)right--; // right-- 所在的值如果和right一样,则重复寻找了,因此寻找一个和原来right不一样的,即去重
                        while(left<right && nums[left] == leftnum)left++;  // 同理,去重
                    }
                    else if(leftnum+rightnum < target){
                        while(left<right && nums[left] == leftnum)left++; // 同理,去重
                    } 
                    else if(leftnum+rightnum > target){
                        while(left<right && nums[right]==rightnum)right--;// 同理,去重
                    }
                }
                return res;
        }else{ // 先定下一个数,将问题规模缩小
            
            for(int i = start; i < nums.length;i++){
                int cur = nums[i]; 
                List<List<Integer>> re = nSumTarget(nums,target-cur,i+1,needNum-1); // 递归调用
                
                for(List<Integer> r:re){
                    r.add(cur);// 将定下的数加入到每个字问题的答案中
                    res.add(r);// 将r加入到最终答案中
                }
                while(i+1 < nums.length && nums[i]==nums[i+1])  i++; // 同理,去重
            }
            return res; // 最终答案
            
        }
    }
}

42. 接雨水 困难 🔥

题目链接

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

在这里插入图片描述


思路:
就像木桶盛水一样,每个位置的盛水量受左右短边的影响。
在这里插入图片描述

除了最两边的位置不可能盛水以外,中间位置的盛水量受左右两边高度的影响,主要取决于较小的一边的最大高度。

代码:

class Solution {
    public int trap(int[] height) {


        int len = height.length;

        if(len<=2)return 0; // base case 

        int[] leftMaxHeight = new int[len]; // 计算每个节点左侧出现过的最大高度(包括当前节点)
        int[] rightMaxHeight = new int[len];// 计算每个节点右侧出现过的最大高度(包括当前节点)

        int leftMax = 0;
        for(int i = 0; i < len;i++){
            leftMax = Math.max(leftMax,height[i]);
            leftMaxHeight[i] = leftMax;
        }
        int rightMax = 0;
        for(int i = len-1; i >= 0; i--){
            rightMax = Math.max(rightMax,height[i]);
            rightMaxHeight[i] = rightMax;
        }
        
        int res = 0;
        for(int i = 1;i< len-1;i++){
            res += Math.min(leftMaxHeight[i],rightMaxHeight[i]) - height[i];
        }
        return res;
    }
}

是否可以再次简化?

通过定义左右双指针,left = 0right = length-1,同时求出leftMaxHeight和rightMaxHeight。并在这个过程中,求出每个位置的盛水量。
在这里插入图片描述

class Solution {
    public int trap(int[] height) {


        int len = height.length;

        if(len<=2)return 0; // base case 

        // 左右指针
        int left = 0,right = len - 1;

        // 记录左/右指针的左/右边的最大值(包括左/右节点)
        int leftMax = 0;
        int rightMax = 0;

        int res = 0;

        while(left<=right){
            leftMax = Math.max(leftMax,height[left]);
            rightMax = Math.max(rightMax,height[right]);

            if(leftMax < rightMax){
                // 此时,left位置的 Min(左边最大,右边最大)已经确定,就是【左边最大】,即leftMax
                res += leftMax - height[left];
                left++;
            }else{
                // 同理
                res += rightMax - height[right];
                 right--;
            }
        }

        return res;
    }
}

26. 删除有序数组中的重复项 简单

题目链接

给你一个 升序排列 的数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。元素的 相对顺序 应该保持 一致 。然后返回 nums 中唯一元素的个数。

考虑 nums 的唯一元素的数量为 k ,你需要做以下事情确保你的题解可以被通过:

更改数组 nums ,使 nums 的前 k 个元素包含唯一元素,并按照它们最初在 nums 中出现的顺序排列。nums 的其余元素与 nums 的大小不重要。
返回 k 。

示例 1:

输入:nums = [1,1,2]
输出:2, nums = [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]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。


思路:快慢指针,快指针如果不等于慢指针,就将快指针的值赋给慢指针的下一个。

代码:

class Solution {
    public int removeDuplicates(int[] nums) {
        int left = 0;
        int right = 0;
        while(right<=nums.length-1){
            if(nums[right]!=nums[left]){
                nums[++left] = nums[right];
                right++;
            }else{
                right++;
            }
        }
        return left+1;

    }
}

5. 最长回文子串 中等

题目链接

给你一个字符串 s,找到 s 中最长的回文子串。

如果字符串的反序与原始字符串相同,则该字符串称为回文字符串。

示例 1:
输入:s = “babad”
输出:“bab”
解释:“aba” 同样是符合题意的答案。

示例 2:
输入:s = “cbbd”
输出:“bb”


思路:

  • 通过双指针从中间向外扩,判断回文
  • 依次选择原字符串的中间节点
  • 注意奇数数量和偶数数量的回文串中间节点个数不一样

代码:

class Solution {
    public String longestPalindrome(String s) {
		String result = null;

		result = s.substring(0,1);

		String r;
		// 遍历每一个字符,将其作为回文串的中心向外扩展
		for(int i = 1; i < s.length(); i++) {
			r = Palindrome(s,i,i);// 寻找奇数个的回文子串
			if(r.length()>result.length())result = r;
			r = Palindrome(s,i-1,i);// 寻找偶数个的回文子串
			if(r.length()>result.length())result = r;
		}
		return result;
	}

	/**
	 * 返回以s[a]与s[a]为中心的最大回文字串,a可以等于b 或者 a+1 = b
	 * ps:一种回文串为偶数个,中心两个字母、另一种为奇数个,中心一个字母
	 * @param s
	 * @param a
	 * @param b
	 * @return
	 */
	public String Palindrome(String s,int a,int b) {

		// 若s[a]!=s[b] return ""
		if(s.charAt(a) != s.charAt(b))return "";

		while (a >= 0 && b<s.length() ){

			if(s.charAt(a) == s.charAt(b)){
				a--;b++;
			}else break;
		}
		a = a+1;
		b = b-1;
		return s.substring(a,b+1);
	}
}

参考:
1.《labuladong的算法小抄》
2. https://labuladong.github.io/algo/di-yi-zhan-da78c/shou-ba-sh-48c1d/shuang-zhi-fa4bd/
3. LeetCode Hot 100 🔥

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值