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]
说明:
- 必须在原数组上操作,不能拷贝额外的数组。
- 尽量减少操作次数。
题解:
定义[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
个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
此题中,我们使用整数 0
、 1
和 2
分别表示红色、白色和蓝色。
示例 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]
为0
、1
或2
进阶:
- 你可以不使用代码库中的排序函数来解决这道题吗?
- 你能想出一个仅使用常数空间的一趟扫描算法吗?
题解:
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:
输入:[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 的字母异位词的子串,返回这些子串的起始索引。
字符串只包含小写英文字母,并且字符串 s 和 p 的长度都不超过 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" 的字母异位词。
题解: 这道题与下面一道题的解法一致。
- 这道题的思想是滑动窗口,因为我们要判断某个元素数量是否满足要求,可以用map来存储,那么如何判断我们要的元素是否都齐了呢?可以用一个int变量来记录。
- 窗口两个指针,当窗口内的元素满足需要的时候,移动左指针,将窗口左边的第一个相关元素移除。然后继续移动右指针,试图寻找一个最小的包含所有元素的窗口。
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
s
和t
由英文字母组成
题解:
- 这道题的思想是滑动窗口,因为我们要判断某个元素数量是否满足要求,可以用map来存储,那么如何判断我们要的元素是否都齐了呢?可以用一个int变量来记录。
- 窗口两个指针,当窗口内的元素满足需要的时候,移动左指针,将窗口左边的第一个相关元素移除。然后继续移动右指针,试图寻找一个最小的包含所有元素的窗口。
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);
}
}