42.接雨水:https://leetcode-cn.com/problems/trapping-rain-water/
点击查看代码
public class Solution {
public int trap(int[] height) {
int size = height.length;
if (size <= 1) {
return 0;
}
int[] lh = new int[size];
int[] rh = new int[size];
lh[0] = 0;
for (int i = 1; i < size; i++) {
lh[i] = Math.max(lh[i - 1], height[i-1]);
}
rh[size - 1] = 0;
for (int i = size - 2; i >= 0; i--) {
rh[i] = Math.max(rh[i + 1], height[i+1]);
}
int sum = 0;
for (int i = 1; i <= size - 2; i++) {
// 第i列可装雨水量为左边或右边最高度-当前列的高度:max(0, min(lh, rh) - curh)
sum += Math.max(Math.min(lh[i], rh[i]) - height[i], 0);
}
return sum;
}
}
点击查看代码
// 找规律,整数拆分结果只包含2和3,2和3的个数具有一定规律和关联。时间复杂度为O(1)
public class Solution {
int integerBreak(int n) {
if (n < 4) {
return n - 1;
}
int count3 = n / 3;
if (n % 3 == 1) {
count3--;
}
int count2 = (n - 3 * count3) / 2;
return (int) (Math.pow(3, count3) * Math.pow(2, count2));
}
}
// 子问题拆分。分析:f(n) = max(f(i) * f(n-i)), i=1……n/2,分析时自上而下,求解时自下而上。时间复杂度O(n^2)
public class Solution {
public int integerBreak(int n) {
if (n < 4) {
return n - 1;
}
int[] f = new int[n + 1];
// 自下而上求解,当n大于4时,分段1、2、3无需再拆解已是最大值
f[1] = 1;
f[2] = 2;
f[3] = 3;
for (int i = 4; i <= n; i++) {
int res = 0;
for (int j = 1; j <= i / 2; j++) {
res = Math.max(res, f[j] * f[i - j]);
}
f[i] = res;
}
return f[n];
}
}
点击查看代码
public class Solution {
public boolean lemonadeChange(int[] bills) {
int bill5 = 0;
int bill10 = 0;
for (int bill : bills) {
if (bill == 5) {
bill5++;
} else if (bill == 10) {
if (bill5 <= 0) {
return false;
}
bill5--;
bill10++;
} else {
// 收到20元时,优先找10元,没有时再全部使用5元
if (bill10 > 0 && bill5 > 0) {
bill10--;
bill5--;
} else if (bill5 >= 3) {
bill5 -= 3;
} else {
return false;
}
}
}
return true;
}
}
点击查看代码
// 本题使用hash数组;由于原数组有序,本题也可使用双指
class Solution {
public int[] sortedSquares(int[] nums) {
int maxSize = 10001;
// 创建哈希数组,tmp[i]表示nums数组中取值为i的元素个数
int[] tmp = new int[maxSize];
for (int num : nums) {
if (num < 0) {
num = -num;
}
tmp[num]++;
}
int j = 0;
for (int i = 0; i < maxSize; i++) {
if (tmp[i] == 0) {
continue;
}
while (tmp[i] > 0) {
nums[j++] = i * i;
tmp[i]--;
}
}
return nums;
}
}
// 双指针代码参考:https://blog.csdn.net/weixin_44029692/article/details/107050722
- 数组中的 k-diff 数对:https://leetcode-cn.com/problems/k-diff-pairs-in-an-array/
点击查看代码
// 排序+双指针
public class Solution {
public int findPairs(int[] nums, int k) {
if (nums.length == 1) {
return 0;
}
Arrays.sort(nums);
int res = 0;
int pre = 0;
int post = 1;
// 因为要求数组对不重复,这里需要标记一下上一个数组对
int preNum = Integer.MIN_VALUE;
while (post < nums.length) {
if (nums[post] - nums[pre] == k) {
// 避免nums中元素重复导致数组对重复统计
if (nums[pre] != preNum) {
res++;
preNum = nums[pre];
}
pre++;
post++;
} else if (nums[post] - nums[pre] > k) {
// 移动pre指针,不能超过post指针
if (pre + 1 == post) {
post++;
}
pre++;
} else {
// 移动post指针
post++;
}
}
return res;
}
}
// 方法2,借助HashMap去重,时间复杂度O(n)
public class Solution {
public int findPairs(int[] nums, int k) {
HashMap<Integer, Integer> map = new HashMap<>();
for (int i = 0; i < nums.length; i++) {
// 记录元素值和下标
map.put(nums[i], i);
}
ArrayList<Pair<Integer, Integer>> res = new ArrayList<>();
for (Map.Entry<Integer, Integer> entry : map.entrySet()) {
int tmp = k - entry.getKey();
// 确保k和tmp都在map中且不是nums中的同一个元素
if (map.containsKey(tmp) && !map.get(tmp).equals(entry.getValue())) {
res.add(new Pair<>(entry.getValue(), map.get(tmp)));
}
}
// (k,tmp)及(tmp,k)都在res中,如果不需要数据具体数组对,可不用ArrayList,直接统计符合条件的数组对个数
return res.size()/2;
}
}
点击查看代码
// 双指针:有序数组原地合并
public class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
int i = m - 1;
int j = n - 1;
int curLoc = m + n - 1;
while (i >= 0 && j >= 0) {
if (nums1[i] > nums2[j]) {
nums1[curLoc--] = nums1[i--];
} else {
nums1[curLoc--] = nums2[j--];
}
}
// 不用再单独处理i >= 0的场景
while (j >= 0) {
nums1[curLoc--] = nums2[j--];
}
}
}
点击查看代码
// 处于位置i时判断: i及之前的位置所能跳的最远距离,如果落在i,则false,如果超过n-1,则true,否则继续
public class Solution {
public boolean canJump(int[] nums) {
int rightMost = 0;
// 最后一个位置不用再判断,所以是nums.length-1
for (int i = 0; i < nums.length - 1; i++) {
rightMost = Math.max(rightMost, i + nums[i]);
if (rightMost == i) {
return false;
} else if (rightMost >= nums.length - 1) {
return true;
}
}
return true;
}
}
- 排序数组,手写归并排序、快排、堆排:https://leetcode-cn.com/problems/sort-an-array/
点击查看代码
// 归并排序实现
public class Solution {
public int[] sortArray(int[] nums) {
if (nums.length <= 1) {
return nums;
}
// 归并
mergeSort(nums, 0, nums.length - 1);
return nums;
}
public void mergeSort(int[] nums, int low, int high) {
if (low >= high) {
return;
}
int mid = (low + high) / 2;
// 向下划分
mergeSort(nums, low, mid);
mergeSort(nums, mid + 1, high);
// 向上合并
merge(nums, low, mid, high);
}
public void merge(int[] nums, int low, int mid, int high) {
int[] tmp = new int[high - low + 1];
int cur = 0;
// 合并有序数组
int i = low;
int j = mid + 1;
while (i <= mid && j <= high) {
if (nums[i] <= nums[j]) {
tmp[cur++] = nums[i++];
} else {
tmp[cur++] = nums[j++];
}
}
while (i <= mid) {
tmp[cur++] = nums[i++];
}
while (j <= high) {
tmp[cur++] = nums[j++];
}
// 复制临时数组到nums
for (int k = 0; k < tmp.length; k++) {
nums[low + k] = tmp[k];
}
}
}
// 堆排实现:堆本质上时一个完全二叉树,底层数据结构使用数组即可
// 建堆:由下向上排序;维护堆:新数据覆盖堆顶,由上向下调整
public class Solution {
public int[] sortArray(int[] nums) {
// 建大顶堆,由下向上调整
for (int i = 1; i < nums.length; i++) {
adjustDownToUp(i, nums);
}
// 维护堆,将堆顶元素和后面的元素交换,再维护[0,n-1]区间的大顶堆,直到整个数组升序排序完成
for (int j = nums.length - 1; j > 0; j--) {
// 元素交换
swap(0, j, nums);
// 剩下区间维护大顶堆。由上向下调整
adjustUpToDown(j - 1, nums);
}
return nums;
}
public void adjustDownToUp(int numMax, int[] nums) {
int par = numMax;
while (par > 0) {
// 依次找出父节点,左孩子,右孩子,并比较(符合大顶堆定义)。由下xiang
par = (par - 1) / 2;
int lchild = par * 2 + 1;
int rchild = par * 2 + 2;
// tmp用以保存取值较大的孩子编号,左孩子一定存在
int tmp = lchild;
if (rchild <= numMax && nums[lchild] < nums[rchild]) {
tmp = rchild;
}
// 如果不需要交换,则已经满足堆定义,直接break
if (nums[tmp] <= nums[par]) {
break;
}
swap(tmp, par, nums);
}
}
public void adjustUpToDown(int numMax, int[] nums) {
int par = 0;
while (par < numMax) {
// 依次找出父节点,左孩子,右孩子,并比较(符合大顶堆定义)
int lchild = par * 2 + 1;
int rchild = par * 2 + 2;
if (lchild > numMax) {
break;
}
// tmp用以保存取值较大的孩子编号,左孩子一定存在
int tmp = lchild;
if (rchild <= numMax && nums[tmp] < nums[rchild]) {
tmp = rchild;
}
// 如果不需要交换,则已经满足堆定义,直接break
if (nums[tmp] <= nums[par]) {
break;
}
swap(tmp, par, nums);
par = tmp;
}
}
public void swap(int i, int j, int[] nums) {
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
}
}
// 快排实现
public class Solution {
public int[] sortArray(int[] nums) {
if (nums.length <= 1) {
return nums;
}
// 快排
quickSort(nums, 0, nums.length - 1);
return nums;
}
public void quickSort(int[] nums, int low, int high) {
int i = low;
int j = high;
if (i < j) {
// 选择支点
int pivot = nums[i];
while (i < j) {
while (i < j && nums[j] >= pivot) {
j--;
}
nums[i] = nums[j];
while (i < j && nums[i] < pivot) {
i++;
}
nums[j] = nums[i];
}
nums[i] = pivot;
quickSort(nums, low, i - 1);
quickSort(nums, i + 1, high);
}
}
}
点击查看代码
// 题目要求时间复杂度为O(n),题解:桶排+鸽笼原理
// 鸽笼原理:https://blog.csdn.net/m0_37433111/article/details/108401807
// 鸽笼原理结论: maxGap >= avgGap = bucketGap
public class Solution {
public int maximumGap(int[] nums) {
if (nums.length <= 1) {
return 0;
}
// 获取最大值和最小值
int minNum = Arrays.stream(nums).min().getAsInt();
int maxNum = Arrays.stream(nums).max().getAsInt();
if (maxNum == minNum) {
return 0;
}
// 获取桶的长度(桶内元素的取值范围区间)
int bkgap = (maxNum - minNum) / nums.length + 1;
// 获取桶的个数
int bknum = (maxNum - minNum) / bkgap + 1;
// 创bknum个桶,每个桶只记录最大值和最小值,因此桶内元素个数为2,初始值均为-1
int[][] buckets = new int[bknum][2];
for (int i = 0; i < bknum; i++) {
Arrays.fill(buckets[i], -1);
}
// 将nums中的数放到各个桶中(实际只更新最大值和最小值)
for (int num : nums) {
int loc = (num - minNum) / bkgap;
if (buckets[loc][0] == -1) {
buckets[loc][0] = buckets[loc][1] = num;
} else {
buckets[loc][0] = Math.min(buckets[loc][0], num);
buckets[loc][1] = Math.max(buckets[loc][1], num);
}
}
// 最大间距 = 下一个有元素的桶的最小值 - 上一个有元素的桶的最大值
int res = Integer.MIN_VALUE;
int pre = -1;
for (int curLoc = 0; curLoc < bknum; curLoc++) {
if (buckets[curLoc][0] == -1) {
// 桶内没有元素
continue;
}
if (pre != -1) {
res = Math.max(res, buckets[curLoc][0] - buckets[pre][1]);
}
pre = curLoc;
}
return res;
}
}
剑指 Offer 57 - II. 和为s的连续正数序列:https://leetcode-cn.com/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/
点击查看代码
// 双指针维护一个滑动窗口区间值,判断该区间值的和是否==target
class Solution {
public int[][] findContinuousSequence(int target) {
ArrayList<int[]> res = new ArrayList<>();
int pre = 1;
int post = 2;
// 有序序列区间[pre, post]的和
int sum = 3;
while (post <= target / 2 + 1) {
if (sum == target) {
int[] tmp = new int[post - pre + 1];
for (int i = pre; i <= post; i++) {
tmp[i - pre] = i;
}
res.add(tmp);
post++;
sum += post;
} else if (sum < target) {
post++;
sum += post;
} else {
sum -= pre;
// 某些场景移动pre指针后,需要判断并处理pre >= post的场景
pre++;
}
}
return res.toArray(new int[res.size()][]);
}
}
//用双指针维护滑动区间值 场景变形:某一个大文件被拆成了N个小文件,每个小文件编号从0至N-1,相应大小分别记为S(i)。给定磁盘空间为C,试实
//现一个函数从N个文件中连续选出若干个文件拷贝到磁盘中,使得磁盘剩余空间最小。
点击查看代码
// 思路1:先排序再求解,或者直接使用TreeSet,时间复杂度均为O(nLogn)
// 正确题解:使用HashSet,找到每个序列开始的元素,再统计长度
public class Solution {
public int longestConsecutive(int[] nums) {
if (nums.length <= 1) {
return nums.length;
}
// HashSet和HashMap可去重,元素无序,查找某元素的时间复杂度为O(1)
HashSet<Integer> set = new HashSet<>();
for (int num : nums) {
set.add(num);
}
int res = 0;
for (int num : set) {
// 判断num是否为连续序列的第一个数字,这是O(n)时间复杂度的关键
if (!set.contains(num - 1)) {
int curNum = num;
int curLen = 1;
while (set.contains(curNum + 1)) {
curNum++;
curLen++;
}
res = Math.max(res, curLen);
}
}
return res;
}
}
点击查看代码
public class Solution {
public int[] topKFrequent(int[] nums, int k) {
// 统计各数出现的频次
HashMap<Integer, Integer> map = new HashMap();
for (int num : nums) {
map.put(num, map.getOrDefault(num, 1));
}
// 使用小顶堆维护频次最高的前k个数, 默认是大顶堆
PriorityQueue<Integer> pq = new PriorityQueue<>(new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return map.get(o1) - map.get(o2);
}
});
// 直接遍历map(去重+统计频次)
for (int num : map.keySet()) {
if (pq.size() < k) {
pq.add(num);
} else if (map.get(num) > map.get(pq.peek())) {
// 堆顶元素出现频次小于当前元素的频次
pq.remove();
pq.add(num);
}
}
int[] res = new int[pq.size()];
int len = 0;
while (!pq.isEmpty()) {
res[len++] = pq.remove();
}
return res;
}
}
点击查看代码
/ 输出给定数字下一个比它大的数字。java中的util类:Arrays, Collections。数学类:Math
// 求解思路: 从后往前找转折点,满足:num[i] < num[i+1]. 然后从i+1往后找比num[i]大的所有数中最小的数num[j].
// 交换num[i]和num[j]后通过排序是的num[i+1]之后的数由小到大排序
public class Solution {
public void nextPermutation(int[] nums) {
if (nums.length <= 1) {
return;
}
// 查找转折点i
int i = nums.length - 2;
while (i >= 0 && nums[i] >= nums[i+1]) {
i--;
}
if (i < 0) {
// 当前数已经最大,直接反转
Arrays.sort(nums);
return;
}
// 查找可交换的元素j.注意:i+1之后的元素一定为递减序列,因此从后往前找即可
int j = nums.length - 1;
while (nums[j] <= nums[i]) {
j--;
}
// 交换元素num[i]和num[j]
int tmp = nums[i];
nums[i] = nums[j];
nums[j] = tmp;
// 确保i+1之后的元素递增
Arrays.sort(nums, i+1, nums.length);
}
}
点击查看代码
// 时间复杂度为O(n)
class Solution {
public boolean checkInclusion(String s1, String s2) {
if (s1.length() > s2.length()) {
return false;
}
int size = 26;
int[] s1Arr = new int[size];
int[] s2Arr = new int[size];
for (int i = 0; i < s1.length(); i++) {
s1Arr[s1.charAt(i) - 'a']++;
s2Arr[s2.charAt(i) - 'a']++;
}
if (isSame(s1Arr, s2Arr)) {
return true;
}
for (int j = s1.length(); j < s2.length(); j++) {
// 统计s2中长度为s1.length的连续子串,并比较
// 去掉首个字符,新增一个字符
s2Arr[s2.charAt(j - s1.length()) - 'a']--;
s2Arr[s2.charAt(j) - 'a']++;
if (isSame(s1Arr, s2Arr)) {
return true;
}
}
return false;
}
public boolean isSame(int[] s1Arr, int[] s2Arr) {
// 比较s1Arr和s2Arr中的数据是否一致
for (int i = 0; i < s1Arr.length; i++) {
if (s1Arr[i] != s2Arr[i]) {
return false;
}
}
return true;
}
}
艾子的世界艾子主宰