LeetCode - 1. 数组
刷题顺序来自:代码随想录
二分查找
704. 二分查找
给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。
示例1:
输入: nums = [-1,0,3,5,9,12], target = 9
输出: 4
解释: 9 出现在 nums 中并且下标为 4
示例2:
输入: nums = [-1,0,3,5,9,12], target = 2
输出: -1
解释: 2 不存在 nums 中因此返回 -1
左闭右闭方法
public static int search1(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int result = -1;
while (left <= right) {
int index = (left + right) / 2;
int mid = nums[index];
if (mid < target) {
left = index + 1;
}
else if (mid > target) {
right = index - 1;
}
else {
result = index;
break;
}
}
return result;
}
左闭右开
public static int search2(int[] nums, int target) {
int left = 0;
int right = nums.length; // 不同
int result = -1;
// 不同
while (left < right) {
int index = (left + right) / 2;
int mid = nums[index];
if (mid < target) {
left = index + 1;
}
else if (mid > target) {
right = index; // 不同
}
else {
result = index;
break;
}
}
return result;
}
35. 搜索插入位置
给定一个排序数组和一个目标值,在数组中找到目标值,并返回其索引。如果目标值不存在于数组中,返回它将会被按顺序插入的位置。
请必须使用时间复杂度为 O(log n) 的算法。
示例1:
输入: nums = [1,3,5,6], target = 5
输出: 2
示例2:
输入: nums = [1,3,5,6], target = 0
输出: 0
public static int searchInsert(int[] nums, int target) {
int left = 0;
int right = nums.length - 1;
int result = -1;
int mid = 0;
while (left <= right) {
mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid - 1;
}
else {
result = mid;
return result;
}
}
// 注意插入位置
// 如果目标值更小,在当前位置插入
// 如果目标值更大,在当前位置前一个位置插入
if (nums[mid] < target) {
result = mid + 1;
}
else {
result = mid;
}
return result;
}
34. 在排序数组中查找元素的第一个和最后一个位置
给定一个按照升序排列的整数数组 nums,和一个目标值 target。找出给定目标值在数组中的开始位置和结束位置。
如果数组中不存在目标值 target,返回 [-1, -1]。
示例1:
输入:nums = [5,7,7,8,8,10], target = 8
输出:[3,4]
示例2:
输入:nums = [5,7,7,8,8,10], target = 6
输出:[-1,-1]
public static int[] searchRange(int[] nums, int target) {
int[] result = {-1, -1};
int left = 0;
int right = nums.length - 1;
while (left <= right) {
int mid = (left + right) / 2;
if (nums[mid] < target) {
left = mid + 1;
}
else if (nums[mid] > target) {
right = mid - 1;
}
// 找到目标
else {
result[0] = mid;
result[1] = mid;
// 左下标左移
while (result[0] - 1 >= 0 && nums[result[0]-1] == target) {
result[0]--;
}
// 右下标右移
while (result[1] + 1 < nums.length && nums[result[1]+1] == target) {
result[1]++;
}
break;
}
}
return result;
}
69. sqrt(x)
给你一个非负整数 x ,计算并返回 x 的 算术平方根 。
由于返回类型是整数,结果只保留整数部分 ,小数部分将被舍去 。
注意:不允许使用任何内置指数函数和算符,例如 pow(x, 0.5) 或者 x ** 0.5 。
public static int mySqrt(int x) {
if (x <= 1) {
return x;
}
int left = 1;
int right = x / 2;
while (left <= right) {
int mid = (left + right) / 2;
int result = x / mid;
if (result > mid) {
left = mid + 1;
}
else if (result < mid) {
right = mid - 1;
}
else {
return mid;
}
}
// 当left > right时,应当返回right
return right;
}
367. 有效的完全平方数
给定一个 正整数 num ,编写一个函数,如果 num 是一个完全平方数,则返回 true ,否则返回 false 。
示例1:
输入:num = 16
输出:true
示例2:
输入:num = 14
输出:false
public static boolean isPerfectSquare(int num) {
if (num <= 1) {
return true;
}
int left = 1;
int right = num / 2;
int mid = 1;
while (left <= right) {
mid = (left + right) / 2;
int result = num / mid;
if (result > mid) {
left = mid + 1;
}
else if (result < mid) {
right = mid - 1;
}
else {
break;
}
}
return num / mid == mid && num % mid == 0;
}
移除元素
27. 移除元素
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用O(1)额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
public static int removeElement(int[] nums, int val) {
int count = 0; // 记录需要移除元素的数量
for (int i = 0; i < nums.length; i++) {
// 当前元素需要被移除
if (nums[i] == val) {
count++;
} // 如果不需要被移除,则前移
else {
nums[i - count] = nums[i];
}
}
return nums.length - count;
}
原理相同的快慢指针法(C++版):
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
if (val != nums[fastIndex]) {
nums[slowIndex++] = nums[fastIndex];
}
}
return slowIndex;
}
};
26. 删除有序数组中的重复项
给你一个有序数组 nums ,请你原地删除重复出现的元素,使每个元素只出现一次 ,返回删除后数组的新长度。
不要使用额外的数组空间,你必须在原地修改输入数组并在使用O(1)额外空间的条件下完成。
快慢指针法:
public static int removeDuplicates(int[] nums) {
if (nums.length <= 1) {
return nums.length;
}
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[slow] != nums[fast]) {
nums[++slow] = nums[fast];
}
}
return slow + 1;
}
283. 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
public static void moveZeroes(int[] nums) {
int count = 0; // 记录0的个数
for (int i = 0; i < nums.length; i++) {
// 记录0元素个数
if (nums[i] == 0) {
count++;
}
// 将非0元素前移
else {
nums[i - count] = nums[i];
}
}
// 将数组尾部修改为0
for (int i = 0; i < count; i++) {
nums[nums.length - 1 - i] = 0;
}
}
官方解法,交换0和非0元素:
class Solution {
public void moveZeroes(int[] nums) {
int n = nums.length, left = 0, right = 0;
while (right < n) {
if (nums[right] != 0) {
swap(nums, left, right);
left++;
}
right++;
}
}
public void swap(int[] nums, int left, int right) {
int temp = nums[left];
nums[left] = nums[right];
nums[right] = temp;
}
}
844. 比较含退格的字符串
给定 s 和 t 两个字符串,当它们分别被输入到空白的文本编辑器后,请你判断二者是否相等。# 代表退格字符。
如果相等,返回 true ;否则,返回 false 。
注意:如果对空文本输入退格字符,文本继续为空。
public static boolean backspaceCompare(String s, String t) {
char[] sChars = s.toCharArray();
char[] tChars = t.toCharArray();
int sSlow = backspace(sChars);
int tSlow = backspace(tChars);
// 比较2个字符串是否相等
if (sChars.length - sSlow != tChars.length - tSlow) {
return false;
}
else {
for (int i = 0; i < sChars.length - sSlow; i++) {
if (sChars[sSlow + i] != tChars[tSlow + i]) {
return false;
}
}
}
return true;
}
// 输入"ab###c"
// 数组修改为"#####c",返回第一个有效元素索引5
public static int backspace(char[] chars) {
int slow = chars.length - 1;
int count = 0; // 记录当前退格键的个数
for(int fast = chars.length - 1; fast >= 0; fast--) {
// 分为3种情况
if (chars[fast] == '#') {
count++;
}
else {
if (count == 0) {
swap(chars, slow, fast);
slow--;
}
else {
chars[fast] = '#';
count--;
}
}
}
return slow + 1;
}
// 交换char数组的两个位置
public static void swap(char[] chars, int i, int j) {
char temp = chars[i];
chars[i] = chars[j];
chars[j] = temp;
}
官方解法:
重构字符串
具体地,我们用栈处理遍历过程,每次我们遍历到一个字符:
- 如果它是退格符,那么我们将栈顶弹出;
- 如果它是普通字符,那么我们将其压入栈中。
class Solution {
public boolean backspaceCompare(String S, String T) {
return build(S).equals(build(T));
}
public String build(String str) {
StringBuffer ret = new StringBuffer();
int length = str.length();
for (int i = 0; i < length; ++i) {
char ch = str.charAt(i);
if (ch != '#') {
ret.append(ch);
} else {
if (ret.length() > 0) {
ret.deleteCharAt(ret.length() - 1);
}
}
}
return ret.toString();
}
}
双指针法
一个字符是否会被删掉,只取决于该字符后面的退格符,而与该字符前面的退格符无关。因此当我们逆序地遍历字符串,就可以立即确定当前字符是否会被删掉。
具体地,我们定义skip表示当前待删除的字符的数量。每次我们遍历到一个字符:
- 若该字符为退格符,则我们需要多删除一个普通字符,
skip++。 - 若该字符为普通字符:
- 若
skip为0,则说明当前字符不需要删去; - 若
skip不为0,则说明当前字符需要删去,skip++。
- 若
这样,我们定义两个指针,分别指向两字符串的末尾。每次我们让两指针逆序地遍历两字符串,直到两字符串能够各自确定一个字符,然后将这两个字符进行比较。重复这一过程直到找到的两个字符不相等,或遍历完字符串为止。
class Solution {
public boolean backspaceCompare(String S, String T) {
int i = S.length() - 1, j = T.length() - 1;
int skipS = 0, skipT = 0;
while (i >= 0 || j >= 0) {
while (i >= 0) {
if (S.charAt(i) == '#') {
skipS++;
i--;
} else if (skipS > 0) {
skipS--;
i--;
} else {
break;
}
}
while (j >= 0) {
if (T.charAt(j) == '#') {
skipT++;
j--;
} else if (skipT > 0) {
skipT--;
j--;
} else {
break;
}
}
if (i >= 0 && j >= 0) {
if (S.charAt(i) != T.charAt(j)) {
return false;
}
} else {
if (i >= 0 || j >= 0) {
return false;
}
}
i--;
j--;
}
return true;
}
}
977. 有序数组的平方
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
public static int[] sortedSquares(int[] nums) {
// 用来存放平方数的新数组
int[] squareNums = new int[nums.length];
int right = -1; // 指向第一个非负元素的索引的前一位
for (int i = 0; i < nums.length; i++) {
if (nums[i] < 0) {
right++;
}
else {
break;
}
}
int left = right; // 最后一个负数的索引
right++; // 第一个非负数的索引
for (int i = 0; i < nums.length; i++) {
// 左指针有效且右指针无效
// 或左指针有效或左指针的绝对值小于右指针
if (left >= 0 && ((right >= nums.length) || -nums[left] <= nums[right])) {
squareNums[i] = nums[left] * nums[left];
left--;
}
else {
squareNums[i] = nums[right] * nums[right];
right++;
}
}
return squareNums;
}
官方答案:双指针从数组头和尾开始,逆序地将元素的平方放入新数组。
public static int[] sortedSquares1(int[] nums) {
// 用来存放平方数的新数组
int[] squareNums = new int[nums.length];
int left = 0;
int right = nums.length - 1;
int count = 0; // 已放置元素数量
while (left <= right) {
if (nums[left] * nums[left] >= nums[right] * nums[right]) {
squareNums[squareNums.length - count - 1] = nums[left] * nums[left];
left ++;
}
else {
squareNums[squareNums.length - count - 1] = nums[right] * nums[right];
right--;
}
count++;
}
return squareNums;
}
滑动窗口
209. 长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 target 。
找出该数组中满足其和 ≥ target 的长度最小的 连续子数组 [nums l, nums l+1, ..., nums r-1, nums r] ,并返回其长度。如果不存在符合条件的子数组,返回 0 。
示例1:
输入:target = 7, nums = [2,3,1,2,4,3]
输出:2
解释:子数组 [4,3] 是该条件下的长度最小的子数组。
示例2:
输入:target = 11, nums = [1,1,1,1,1,1,1,1]
输出:0
使用滑动窗口的思想解题:
- 每次大循环左指针向右移动一格;
- 在一次循环中,右指针一直右移直到:
- 右指针移到末尾
- 或者当前子串和大于等于
target
- 更新
minLen
public static int minSubArrayLen(int target, int[] nums) {
int minLen = -1;
int left = 0;
int right = 0;
int sum = nums[0];
while (left < nums.length) {
// 向右移动,找到符合target的子串
while (right < nums.length - 1 && sum < target) {
right++;
sum += nums[right];
}
// 没有找到符合的子串,提前结束
if (sum < target) {
break;
}
// 更新minLen
int curLen = right - left + 1; // 当前长度
if (minLen == -1 || minLen > curLen) {
minLen = curLen;
}
// 左指针向右移动一格
sum -= nums[left];
left++;
}
return Math.max(minLen, 0);
}
904. 水果成篮
在一排树中,第 i 棵树产生 tree[i] 型的水果。
你可以从你选择的任何树开始,然后重复执行以下步骤:
- 把这棵树上的水果放进你的篮子里。如果你做不到,就停下来。
- 移动到当前树右侧的下一棵树。如果右边没有树,就停下来。
请注意,在选择一颗树后,你没有任何选择:你必须执行步骤 1,然后执行步骤 2,然后返回步骤 1,然后执行步骤 2,依此类推,直至停止。
你有两个篮子,每个篮子可以携带任何数量的水果,但你希望每个篮子只携带一种类型的水果。
用这个程序你能收集的水果树的最大总量是多少?
示例1:
输入:[1,2,1]
输出:3
解释:我们可以收集 [1,2,1]。
示例2:
输入:[0,1,2,2]
输出:3
解释:我们可以收集 [1,2,2]
如果我们从第一棵树开始,我们将只能收集到 [0, 1]。
实际上就是求最长的包含最多2种元素的子串长度。使用变量a和b记录当前子串的两种不同元素,使用aCount和bCount记录它们的数量。
public static int totalFruit(int[] fruits) {
int total = 1;
int a = fruits[0];
int aCount = 1;
int b = -1;
int bCount = 0;
int left = 0;
int right = 1;
while (Math.max(left, right) < fruits.length) {
// 指针右移直到找到第3种元素 或者 移动到数组末尾
while (right < fruits.length) {
if (fruits[right] == a) {
aCount++;
}
else if (fruits[right] == b) {
bCount++;
}
else if (aCount == 0) {
a = fruits[right];
aCount++;
}
else if (bCount == 0) {
b = fruits[right];
bCount++;
}
else {
break;
}
right++;
}
total = Math.max(total, aCount + bCount); // 更新最长子串的值
// 左指针右移,直到当前子串只包含一种元素
while (left < fruits.length && Math.min(aCount, bCount) > 0) {
if (fruits[left] == a) {
aCount--;
}
else if (fruits[left] == b) {
bCount--;
}
left++;
}
}
return total;
}
官方答案:
class Solution {
public int totalFruit(int[] tree) {
int ans = 0, i = 0;
Counter count = new Counter();
for (int j = 0; j < tree.length; ++j) {
count.add(tree[j], 1);
// 出现第3种元素,则左指针右移,直到元素数量小于3
while (count.size() >= 3) {
count.add(tree[i], -1);
if (count.get(tree[i]) == 0)
count.remove(tree[i]);
i++;
}
ans = Math.max(ans, j - i + 1);
}
return ans;
}
}
class Counter extends HashMap<Integer, Integer> {
public int get(int k) {
return containsKey(k) ? super.get(k) : 0;
}
public void add(int k, int v) {
put(k, get(k) + v);
}
}
76. 最小覆盖子串
给你一个字符串 s 、一个字符串 t 。返回 s 中涵盖 t 所有字符的最小子串。如果 s 中不存在涵盖 t 所有字符的子串,则返回空字符串 "" 。
注意:
- 对于
t中重复字符,我们寻找的子字符串中该字符数量必须不少于t中该字符数量。 - 如果
s中存在这样的子串,我们保证它是唯一的答案。
示例1:
输入:s = "ADOBECODEBANC", t = "ABC"
输出:"BANC"
示例2:
输入:s = "a", t = "a"
输出:"a"
示例3:
输入: s = "a", t = "aa"
输出: ""
解释: t 中两个字符 'a' 均应包含在 s 的子串中,
因此没有符合条件的子字符串,返回空字符串。
双指针,右指针每次往右探
public static String minWindow(String s, String t) {
// 目标串各个字符的数量
Counter tCounter = new Counter();
for (int i = 0; i < t.length(); i++) {
char ch = t.charAt(i);
tCounter.add(ch, 1);
}
// 用来记录当前子串的字符数量
Counter sCounter = new Counter();
// 用于记录最小子串的前后索引
int minLeft = 0;
int minRight = 0;
int minCount = s.length() + 1;
int left = 0;
for (int right = 0; right < s.length(); right++) {
// 将当前字符放入Counter
char ch = s.charAt(right);
if (tCounter.containsKey(ch)) {
sCounter.add(ch, 1);
}
// 如果当前串已经覆盖目标串所有字符
// 则左指针右移,直到不能覆盖为止
while (sCounter.cover(tCounter)) {
// 更新最小子串
if (right - left + 1 < minCount) {
minCount = right - left + 1;
minLeft = left;
minRight = right;
}
// 移除左指针元素后,右移一格
if (sCounter.containsKey(s.charAt(left))) {
sCounter.add(s.charAt(left), -1);
}
left++;
}
}
return minCount <= s.length() ? s.substring(minLeft, minRight + 1): "";
}
class Counter extends HashMap<Character, Integer> {
@Override
public Integer get(Object key) {
return containsKey(key) ? super.get(key): 0;
}
public void add(char key, int value) {
put(key, get(key) + value);
}
// 是否覆盖了c中所有字符
public boolean cover(Counter c) {
boolean coverFlag = true;
for (char ch: c.keySet()) {
if (get(ch) < c.get(ch)) {
coverFlag = false;
break;
}
}
return coverFlag;
}
}
由于是字符串,char的类型不会超过256个,所以可以不使用HashMap,直接用数组表示字符出现频率,速度更快。
public static String minWindow1(String s, String t) {
// 目标串各个字符的数量
int[] tChars = new int[256];
for (int i = 0; i < t.length(); i++) {
char ch = t.charAt(i);
tChars[ch]++;
}
// 用来记录当前子串的字符数量
int[] sChars = new int[256];
// 用于记录最小子串的前后索引
int minLeft = 0;
int minRight = 0;
int minCount = s.length() + 1;
int left = 0;
for (int right = 0; right < s.length(); right++) {
// 将当前字符放入Counter
char ch = s.charAt(right);
if (tChars[ch] > 0) {
sChars[ch]++;
}
// 如果当前串已经覆盖目标串所有字符
// 则左指针右移,直到不能覆盖为止
while (cover(sChars, tChars)) {
if (right - left + 1 < minCount) {
minCount = right - left + 1;
minLeft = left;
minRight = right;
}
// 移除左指针元素后,右移一格
if (sChars[s.charAt(left)] > 0) {
sChars[s.charAt(left)]--;
}
left++;
}
}
return minCount <= s.length() ? s.substring(minLeft, minRight + 1): "";
}
public static boolean cover(int[] sChars, int[] tChars) {
boolean coverFlag = true;
for (int i = 0; i < tChars.length; i++) {
if (sChars[i] < tChars[i]) {
coverFlag = false;
break;
}
}
return coverFlag;
}
螺旋矩阵
59. 螺旋矩阵 II
给你一个正整数 n ,生成一个包含 1 到 n2 所有元素,且元素按顺时针顺序螺旋排列的 n x n 正方形矩阵 matrix 。
示例1:
输入:n = 3
输出:[[1,2,3],[8,9,4],[7,6,5]]
示例2:
输入:n = 1
输出:[[1]]
public static int[][] generateMatrix(int n) {
int[][] matrix = new int[n][n];
int count = 0; // 记录现在到第几个数字
for (int i = 0; i < n/2; i++) {
for (int j = 0; j < 4; j++) {
for (int k = 0; k < n-i*2-1; k++) {
count++;
switch (j) {
case 0:
matrix[i][i+k] = count;
break;
case 1:
matrix[i+k][n-i-1] = count;
break;
case 2:
matrix[n-i-1][n-i-1-k] = count;
break;
case 3:
matrix[n-i-1-k][i] = count;
break;
}
}
}
}
if (n % 2 == 1) {
matrix[n/2][n/2] = ++count;
}
return matrix;
}
54. 螺旋矩阵
给你一个 m 行 n 列的矩阵 matrix ,请按照 顺时针螺旋顺序 ,返回矩阵中的所有元素。
示例1:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
public static List<Integer> spiralOrder(int[][] matrix) {
int n = matrix.length;
int m = matrix[0].length;
ArrayList<Integer> list = new ArrayList<>();
for (int i = 0; i < Math.min(n, m)/2; i++) {
for (int j = i; j < m-i-1; j++) {
list.add(matrix[i][j]);
}
for (int j = i; j < n-i-1; j++) {
list.add(matrix[j][m-i-1]);
}
for (int j = i; j < m-i-1; j++) {
list.add(matrix[n-i-1][m-j-1]);
}
for (int j = i; j < n-i-1; j++) {
list.add(matrix[n-j-1][i]);
}
}
if (list.size() < n*m) {
int end = Math.min(n, m)/2;
for (int i = end; i <= n-end-1; i++) {
for (int j = end; j <= m-end-1; j++) {
list.add(matrix[i][j]);
}
}
}
return list;
}
剑指 Offer 29. 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
示例1:
输入:matrix = [[1,2,3,4],[5,6,7,8],[9,10,11,12]]
输出:[1,2,3,4,8,12,11,10,9,5,6,7]
public static int[] spiralOrder(int[][] matrix) {
if (matrix.length == 0 || matrix[0].length == 0) {
return new int[0];
}
int n = matrix.length;
int m = matrix[0].length;
int[] result = new int[n*m];
int pos = -1;
for (int i = 0; i < Math.min(m, n)/2; i++) {
for (int j = 0; j < m-i*2-1; j++) {
result[++pos] = matrix[i][i+j];
}
for (int j = 0; j < n-i*2-1; j++) {
result[++pos] = matrix[i+j][m-i-1];
}
for (int j = 0; j < m-i*2-1; j++) {
result[++pos] = matrix[n-i-1][m-i-1-j];
}
for (int j = 0; j < n-i*2-1; j++) {
result[++pos] = matrix[n-i-1-j][i];
}
}
if (pos < result.length - 1) {
int end = Math.min(m, n)/2;
for (int i = end; i < n-end; i++) {
for (int j = end; j < m-end; j++) {
result[++pos] = matrix[i][j];
}
}
}
return result;
}

浙公网安备 33010602011771号