leetcode——数组、双指针、滑动窗口

1.如何写出正确的算法:明确变量的定义

public int binarySearch(int[] a){
    int l = 0, r = n - 1; // 在[l...r]的范围里寻找target
    while( l <= r){       // 方 l == r 时,区间[l...r]依然是有效的
        int mid = l + (r - l) / 2;
        if(a[mid] == target){
            return mid;
        }
        if(target > a[mid])
            l = mid + 1;  // target在[mid+1...r]中
        else
            r = mid - 1;  // target在[l...mid-1]中
    }
}
public int binarySearch(int[] a){
    int l = 0, r = n; // 在[l...r)的范围里寻找target
    while( l < r){       // 方 l == r 时,区间[l...r)无效的
        int mid = l + (r - l) / 2;
        if(a[mid] == target){
            return mid;
        }
        if(target > a[mid])
            l = mid + 1;  // target在[mid+1...r)中
        else
            r = mid;  // target在[l...mid)中
    }
}
283. 移动零

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

示例:

输入: [0,1,0,3,12]
输出: [1,3,12,0,0]

说明:

  1. 必须在原数组上操作,不能拷贝额外的数组。
  2. 尽量减少操作次数。

题解:
定义[0,k)表示nums数组中非0的元素,k初始化为0;
遍历整个数组,如果是非0的元素,则添加到第k个位置,然后k++;
最后把k~nums.length的元素填为0即可。

class Solution {
    public void moveZeroes(int[] nums) {
        int k = 0;//[0,k)表示nums中的非0元素
        for(int i = 0; i < nums.length; i++){
            if(nums[i] != 0){
                nums[k++] = nums[i];
            }
        }
        for(int i = k; i < nums.length; i++){
            nums[i] = 0;
        }
        
    }
}
26. 删除有序数组中的重复项

给你一个有序数组 nums ,请你 原地 删除重复出现的元素,使每个元素 只出现一次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以「引用」方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 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 。不需要考虑数组中超出新长度后面的元素。

提示:

  • 0 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按升序排列

题解:
定义[0,i)表示nums数组中非重复的元素,k初始化为0;
遍历整个数组,如果是当前元素(num[j])与第i个元素,则添加到i+1的位置,并且i更新为i+1。

class Solution {
    public int removeDuplicates(int[] nums) {
        int i = 0;
        for(int j = 1; j < nums.length; j++){
            if(nums[j] != nums[i]){
                i++;
                nums[i] = nums[j];
            }
        }
        return i + 1;
    }
}
80. 删除有序数组中的重复项 II

给你一个有序数组 nums ,请你** 原地** 删除重复出现的元素,使每个元素 最多出现两次 ,返回删除后数组的新长度。

不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。

说明:

为什么返回数值是整数,但输出的答案是数组呢?

请注意,输入数组是以**「引用」**方式传递的,这意味着在函数里修改输入数组对于调用者是可见的。

你可以想象内部操作如下:

// nums 是以“引用”方式传递的。也就是说,不对实参做任何拷贝
int len = removeDuplicates(nums);

// 在函数里修改输入数组对于调用者是可见的。
// 根据你的函数返回的长度, 它会打印出数组中 该长度范围内 的所有元素。
for (int i = 0; i < len; i++) {
    print(nums[i]);
}

示例 1:

输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 不需要考虑数组中超出新长度后面的元素。

示例 2:

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

提示:

  • 1 <= nums.length <= 3 * 104
  • -104 <= nums[i] <= 104
  • nums 已按升序排列

题解:
前两个元素不管一不一样都先算入满足题意;对应i < 2
接着每个元素与前两个比较,如果大于则算入结果中。(例如:001111只会保留0011)。

class Solution {
    public int removeDuplicates(int[] nums) {
            int i = 0;
		for(int n : nums) {
			if(i < 2 || n > nums[i - 2]) {
				nums[i++] = n;
			}
		}
		return i;
    }
}
75. 颜色分类

给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。

此题中,我们使用整数 012 分别表示红色、白色和蓝色。

示例 1:

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

示例 2:

输入:nums = [2,0,1]
输出:[0,1,2]

示例 3:

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

示例 4:

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

提示:

  • n == nums.length
  • 1 <= n <= 300
  • nums[i]012

进阶:

  • 你可以不使用代码库中的排序函数来解决这道题吗?
  • 你能想出一个仅使用常数空间的一趟扫描算法吗?

题解:
nums[0,zero] == 0;
nums[two, n - 1] == 2;
中间的都是1。那么对于这个设定,zero初始化为-1,two初始化为n,表明这两个范围一开始都没有元素。
在这里插入图片描述

class Solution {
    public void sortColors(int[] nums) {
         int zero = -1;             //nums[0,zero] == 0;
		 int two = nums.length;		//nums[two,n - 1] == 2;
		 for(int i = 0; i < two;) {
			 if(nums[i] == 0) {
				 zero++;
				 int temp = nums[zero];
				 nums[zero] = nums[i];
				 nums[i] = temp;
				 i++;
			 }else if(nums[i] == 2) {
				 two--;
				 int temp = nums[two];
				 nums[two] = nums[i];
				 nums[i] = temp;
			 }else
				 i++;
		 }
    }
}
215. 数组中的第K个最大元素

在未排序的数组中找到第 k 个最大的元素。请注意,你需要找的是数组排序后的第 k 个最大的元素,而不是第 k 个不同的元素。

示例 1:

输入: [3,2,1,5,6,4] 和 k = 2
输出: 5

示例 2:

输入: [3,2,3,1,2,4,5,5,6] 和 k = 4
输出: 4

说明:

你可以假设 k 总是有效的,且 1 ≤ k ≤ 数组的长度。

题解:
就是一个快排,找第nums.length - k个元素。

class Solution {
   Random random = new Random();
	
	public int findKthLargest(int[] nums, int k) {
		int left = 0;
		int right = nums.length - 1;
		int target = nums.length - k;
		while(true) {
			int index = partition(nums, left, right);
			if(index < target) {
				left = index + 1;
			}else if(index > target) {
				right = index - 1;
			}else {
				return nums[index];
			}
		}	
    }
	
	public int partition(int nums[], int left, int right) {
		if(right > left) {
			int randomIndex = left + random.nextInt(right - left + 1);
			swap(nums, left, randomIndex);
		}
		
		int pivot = nums[left];
		int j = left;
		//[0,j] < pivot;
		for(int i = left + 1; i <= right; i++) {
			if(nums[i] < pivot) {
				j++;
				swap(nums, j, i);
			}
		}
		swap(nums, j, left);
		return j;
	}
	
	public void swap(int nums[], int a, int b) {
		int temp = nums[a];
		nums[a] = nums[b];
		nums[b] = temp;
	}
}
167. 两数之和 II - 输入有序数组

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值*。*numbers 的下标 从 1 开始计数 ,所以答案数组应当满足 1 <= answer[0] < answer[1] <= numbers.length

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

示例 1:

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

示例 2:

输入:numbers = [2,3,4], target = 6
输出:[1,3]

示例 3:

输入:numbers = [-1,0], target = -1
输出:[1,2]

提示:

  • 2 <= numbers.length <= 3 * 104
  • -1000 <= numbers[i] <= 1000
  • numbers递增顺序 排列
  • -1000 <= target <= 1000
  • 仅存在一个有效答案

题解:
因为数组有序,所以只需要用双指针往中间移动搜索即可。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
		int right = numbers.length - 1;
		while(left < right) {
			int sum = numbers[left] + numbers[right];
			if(sum < target) {
				left++;
			}else if(sum > target) {
				right--;
			}else {
				return new int[] {left + 1, right + 1};
			}
		}
		return new int[] {0, 0};
    }
}
125. 验证回文串

给定一个字符串,验证它是否是回文串,只考虑字母和数字字符,可以忽略字母的大小写。

**说明:**本题中,我们将空字符串定义为有效的回文串。

示例 1:

输入: "A man, a plan, a canal: Panama"
输出: true

示例 2:

输入: "race a car"
输出: false

双指针遍历,遇到非数字字母即跳过。

class Solution {
    public boolean isPalindrome(String s) {
        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(left < right) {
        		if(Character.toLowerCase(s.charAt(left)) != Character.toLowerCase(s.charAt(right)))
        			return false;
        		++left;
        		--right;
        	}
        }
        return true;
    }
}
345. 反转字符串中的元音字母

编写一个函数,以字符串作为输入,反转该字符串中的元音字母。

示例 1:

输入:"hello"
输出:"holle"

示例 2:

输入:"leetcode"
输出:"leotcede"

提示:

  • 元音字母不包含字母 “y” 。

同双指针

class Solution {
    public String reverseVowels(String s) {
        List list = new ArrayList();
		list.add('a');
		list.add('e');
		list.add('i');
		list.add('o');
		list.add('u');
		list.add('A');
		list.add('E');
		list.add('I');
		list.add('O');
		list.add('U');
		StringBuilder str = new StringBuilder(s);
		int left = 0;
		int right = str.length() - 1;
		while(left < right) {
			while(left < right && !list.contains(str.charAt(left))) 
				 left++;
			while(left < right && !list.contains(str.charAt(right))) 
				 right--;
			if(left < right) {
				char c = str.charAt(left);
				str.setCharAt(left, str.charAt(right));
				str.setCharAt(right, c);
				left++;
				right--;
			}
		}
        return str.toString();
    }
}
11. 盛最多水的容器

给你 n 个非负整数 a1,a2,...,a``n,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai)(i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

**说明:**你不能倾斜容器。

示例 1:

img

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

示例 2:

输入:height = [1,1]
输出:1

示例 3:

输入:height = [4,3,2,1,4]
输出:16

示例 4:

输入:height = [1,2,1]
输出:2

提示:

  • n = height.length
  • 2 <= n <= 3 * 104
  • 0 <= height[i] <= 3 * 104

题解:
双指针,算当前面积,比较sum;
指针移动的问题:因为指针往里移,底的长会变小,所以我们只有要把较矮的直线往里移动,试图找一条更高的,这样面积才有可能变大。

class Solution {
    public int maxArea(int[] height) {
        int left = 0;
		int right = height.length - 1;
		int sum = 0;
		while(left < right) {
			int temp = Math.min(height[left], height[right]) * (right - left);
			if(temp > sum)
				sum = temp;
			if(height[left] < height[right]) {
				left++;
			}else {
				right--;
			}
		}
		return sum;
    }
}
209. 长度最小的子数组

给定一个含有 n 个正整数的数组和一个正整数 target

找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [numsl, numsl+1, ..., numsr-1, numsr] ,并返回其长度**。**如果不存在符合条件的子数组,返回 0

示例 1:

输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。

示例 2:

输入:target = 4, nums = [1,4,4]
输出:1

示例 3:

输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0

提示:

  • 1 <= target <= 109
  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 105

进阶:

  • 如果你已经实现 O(n) 时间复杂度的解法, 请尝试设计一个 O(n log(n)) 时间复杂度的解法。

题解:
利用滑动窗口解题,定义left和right维持一个和 >= target的窗口。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        // 定义一个滑动窗口
        int left = 0;
        int right = 0;
        int n = nums.length;
        int sum = 0;
        int len = 100001;
        while(right < n){
            sum += nums[right];
            right++;
            while(sum >= target){
                if(right - left < len){
                    len = right - left;
                }
                sum -= nums[left];
                left ++;
            }
        }
        if(len == 100001)
            return 0;
        return len;
    }
}
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 由英文字母、数字、符号和空格组成
class Solution {
    public int lengthOfLongestSubstring(String s) {
        int sLen = s.length();
        if(sLen == 0)
            return 0;
        Map<Character, Integer> cur = new HashMap<>();
        // 定义滑动窗口
        int left = 0;
        int right = 0;
        int len = -1;
        while(right < sLen){
            char c = s.charAt(right);
            if(cur.containsKey(c)){
            	// 特殊情况:如abba
                left = Math.max(left, cur.get(c) + 1);
            }
            cur.put(c, right);
            len = Math.max(len, right - left + 1);
            right ++;
        }        
        return len;
    }
}
438. 找到字符串中所有字母异位词

给定一个字符串 s 和一个非空字符串 p,找到 s 中所有是 p 的字母异位词的子串,返回这些子串的起始索引。

字符串只包含小写英文字母,并且字符串 sp 的长度都不超过 20100。

说明:

  • 字母异位词指字母相同,但排列不同的字符串。
  • 不考虑答案输出的顺序。

示例 1:

输入:
s: "cbaebabacd" p: "abc"

输出:
[0, 6]

解释:
起始索引等于 0 的子串是 "cba", 它是 "abc" 的字母异位词。
起始索引等于 6 的子串是 "bac", 它是 "abc" 的字母异位词。

示例 2:

输入:
s: "abab" p: "ab"

输出:
[0, 1, 2]

解释:
起始索引等于 0 的子串是 "ab", 它是 "ab" 的字母异位词。
起始索引等于 1 的子串是 "ba", 它是 "ab" 的字母异位词。
起始索引等于 2 的子串是 "ab", 它是 "ab" 的字母异位词。

题解: 这道题与下面一道题的解法一致。

  1. 这道题的思想是滑动窗口,因为我们要判断某个元素数量是否满足要求,可以用map来存储,那么如何判断我们要的元素是否都齐了呢?可以用一个int变量来记录。
  2. 窗口两个指针,当窗口内的元素满足需要的时候,移动左指针,将窗口左边的第一个相关元素移除。然后继续移动右指针,试图寻找一个最小的包含所有元素的窗口。
class Solution {
    public List<Integer> findAnagrams(String s, String p) {
        Map<Character,Integer> need = new HashMap<>();
        Map<Character,Integer> windows = new HashMap<>();
        List<Integer> list = new ArrayList<>();

        if(s.length() < p.length())
            return new ArrayList<>();
        for(int i = 0; i < p.length(); i++){
            char c = p.charAt(i);
            need.put(c, need.getOrDefault(c,0)+ 1);
        }

        int left = 0; //[0,0)
        int right = 0;
        int valid = 0;
        while(right < s.length()){
            char c = s.charAt(right);
            right++;
            if(need.containsKey(c)){
                windows.put(c, windows.getOrDefault(c, 0) + 1);
                if(need.get(c).intValue() == windows.get(c).intValue())
                    valid ++;
            }
            while(right - left == p.length()){
                if(valid == need.size()) {
                    list.add(left);
                }

                char cc = s.charAt(left);
                left ++;
                if(need.containsKey(cc)){
                    if(need.get(cc).intValue() == windows.get(cc).intValue())
                        valid --;
                    windows.put(cc, windows.get(cc) - 1);
                }
            }
        }
        return list;
    }
}

76. 最小覆盖子串

给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 ""

**注意:**如果 s 中存在这样的子串,我们保证它是唯一的答案。

示例 1:

输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"

示例 2:

输入:s = "a", t = "a"
输出:"a"

提示:

  • 1 <= s.length, t.length <= 105
  • st 由英文字母组成

题解:

  1. 这道题的思想是滑动窗口,因为我们要判断某个元素数量是否满足要求,可以用map来存储,那么如何判断我们要的元素是否都齐了呢?可以用一个int变量来记录。
  2. 窗口两个指针,当窗口内的元素满足需要的时候,移动左指针,将窗口左边的第一个相关元素移除。然后继续移动右指针,试图寻找一个最小的包含所有元素的窗口。
class Solution {
    public String minWindow(String s, String t) {
        Map<Character, Integer> need = new HashMap<>();
        Map<Character, Integer> windows = new HashMap<>();
        int tLen = t.length();
        int sLen = s.length();
        if(tLen == 0 || sLen == 0)
            return "";
        for(int i = 0; i < tLen; i++){
            char c = t.charAt(i);
            need.put(c, need.getOrDefault(c, 0) + 1);
        }
        // 定义窗口左右指针
        int left = 0; 
        int right = 0;
        int len = Integer.MAX_VALUE;
        // 如何判断我们要的元素都有了,用一个变量来记录
        int valid = 0;
        // 要截取一段出来,用一个变量记录起始位置
        int start = 0;
        while(right < sLen){
            char c = s.charAt(right);
            right ++;
            if(need.containsKey(c)){
                windows.put(c, windows.getOrDefault(c, 0) + 1);
                if(windows.get(c).intValue() == need.get(c).intValue()){
                    valid ++;
                }
            }
            while(valid == need.size()){
                if(right - left < len){
                    start = left;
                    len = right - left;
                }
                char cc = s.charAt(left);
                left++;
                if(need.containsKey(cc)){
                    if(need.get(cc).intValue() == windows.get(cc).intValue()){
                        valid --;
                    }
                    windows.put(cc, windows.get(cc) - 1);
                }
            }
        }
        return len == Integer.MAX_VALUE ? "" : s.substring(start, start + len);
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值