文章目录
一、数组实现栈
public static class ArrayStack {
private Integer[] arr;
private Integer size;
public ArrayStack(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
}
/**
* 返回栈顶元素
* @return
*/
public Integer peek() {
if (size == 0) {
return null;
}
return arr[size - 1];
}
/**
* 入栈
* @param num
*/
public void push(int num) {
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
arr[size++] = num;
}
/**
* 出栈
* @return
*/
public Integer pop() {
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
return arr[--size];
}
}
// 自动扩容版本
public class MyStack {
public static void main(String[] args) {
MyStack stack = new MyStack(3);
stack.push(1);
stack.push(2);
stack.push(3);
stack.push(4);
System.out.println(stack.size());
}
private int[] storage; //存放栈中元素的数组
private int capacity; //栈的容量
private int count; //栈中元素数量
private static final int GROW_FACTOR = 2; //扩容因子
//TODO:不带初始容量的构造方法,默认容量为8
public MyStack() {
this.capacity = 8;
this.storage=new int[8];
this.count = 0;
}
//TODO:带初始容量的构造方法
public MyStack(int initialCapacity) {
if (initialCapacity < 1)
throw new IllegalArgumentException("Capacity too small.");
this.capacity = initialCapacity;
this.storage = new int[initialCapacity];
this.count = 0;
}
//TODO:入栈
public void push(int value) {
if (count == capacity) {
ensureCapacity();
}
storage[count++] = value;
}
//TODO:扩容
private void ensureCapacity() {
// 扩容的容量
int newCapacity = capacity * GROW_FACTOR;
// 扩容后的数组,大小是原来的两倍,保留原数组中元素
storage = Arrays.copyOf(storage, newCapacity);
// 更新容量
capacity = newCapacity;
}
//TODO:返回栈顶元素并出栈
private int pop() {
count--;
if (count == -1)
throw new IllegalArgumentException("Stack is empty.");
return storage[count];
}
//TODO:返回栈顶元素不出栈
private int peek() {
if (count == 0){
throw new IllegalArgumentException("Stack is empty.");
}else {
return storage[count-1];
}
}
//TODO:判断栈是否为空
private boolean isEmpty() {
return count == 0;
}
//TODO:返回栈中元素的个数
private int size() {
return count;
}
}
二、数组实现队列
public static class ArrayQueue {
// 存放队列元素的数组
private Integer[] arr;
// 队列中数据的个数
private Integer size;
// 指向队列头
private Integer start;
// 指向队列尾
private Integer end;
public ArrayQueue(int initSize) {
if (initSize < 0) {
throw new IllegalArgumentException("The init size is less than 0");
}
arr = new Integer[initSize];
size = 0;
start = 0;
end = 0;
}
/**
* 返回队列头
* @return
*/
public Integer peek() {
if (size == 0) {
return null;
}
return arr[start];
}
/**
* 添加元素
* @param num
*/
public void push(int num) {
// 判断队列是否已经满了
if (size == arr.length) {
throw new ArrayIndexOutOfBoundsException("The queue is full");
}
// 没有满,大小加一
size++;
// 队列尾赋值
arr[end] = num;
// 跟新队列尾的位置
end = end == arr.length - 1 ? 0 : end + 1;
}
/**
* 出队列
* @return
*/
public Integer poll() {
// 队列为空
if (size == 0) {
throw new ArrayIndexOutOfBoundsException("The queue is empty");
}
// 不为空的时候大小减一
size--;
// 记录当前队列头元素
int tmp = start;
// 更新队列头位置
start = start == arr.length - 1 ? 0 : start + 1;
// 返回队列头元素
return arr[tmp];
}
}
三、稀疏数组
public class SparseArray {
public static void main(String[] args) {
//原始二维数组:10*10
int chessArray1[][] = new int[10][10];
chessArray1[1][2] = 1;
chessArray1[2][3] = 2;
chessArray1[4][5] = 2;
System.out.println("原始数组:");
//1.遍历每一行
for (int[] row : chessArray1) {
//2.遍历每一行的数据
for (int data : row) {
System.out.printf("%d\t",data);
}
System.out.println();
}
/**
* 将二维数组转化为稀疏数组
*/
//1.获取原始数组中不为0的元素的个数
int sum = 0;
for (int[] row : chessArray1) {
for (int data : row) {
if (data != 0) {
sum++;
}
}
}
//2.根据sum创建稀疏数组
int sparseArray[][] = new int [sum+1][3];
sparseArray[0][0] = 10;
sparseArray[0][1] = 10;
sparseArray[0][2] = sum;
//3.将二维数组中的有效数字存到稀疏数组
int count = 0;//记录非0数据的个数
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (chessArray1[i][j] != 0){
count++;
//有效元素在原二维数组中的位置
sparseArray[count][0] = i;
sparseArray[count][1] = j;
//有效元素的值
sparseArray[count][2] = chessArray1[i][j];
}
}
}
System.out.println("稀疏数组:");
//1.遍历每一行
for (int[] row : sparseArray) {
//2.遍历每一行的数据
for (int data : row) {
System.out.printf("%d\t",data);
}
System.out.println();
}
/**
* 将稀疏数组转化为原二维数组
*/
//1.先获取原数组的大小,创建二维数组
int chessArray2[][] = new int[sparseArray[0][0]][sparseArray[0][1]];
//2.从第二行开始遍历稀疏数组,取出值赋值给二维数组
for (int i = 1; i < sparseArray.length; i++) {
chessArray2[sparseArray[i][0]][sparseArray[i][1]] = sparseArray[i][2];
}
System.out.println("恢复后的二维数组:");
//3.输出新的二维数组
for (int[] row : chessArray2) {
//2.遍历每一行的数据
for (int data : row) {
System.out.printf("%d\t",data);
}
System.out.println();
}
}
}
原始数组:
0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 2 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
稀疏数组:
10 10 3
1 2 1
2 3 2
4 5 2
恢复后的二维数组:
0 0 0 0 0 0 0 0 0 0
0 0 1 0 0 0 0 0 0 0
0 0 0 2 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 2 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0
四、数组中的最大差值
public static int maxGap(int[] arr){
if (arr == null || arr.length < 2){
return 0;
}
int length = arr.length;
// 初始化max为最小值
int max = Integer.MIN_VALUE;
// 初始化min为最大值
int min = Integer.MAX_VALUE;
for (int i = 0; i < length; i++) {
//求出数组中的最大值
max = Math.max(max,arr[i]);
//求出数组中的最小值
min = Math.min(min,arr[i]);
}
// 如果数组中只有一个数
if (max == min){
return 0;
}
// 用来标识每个桶中是否有数据(length + 1 个桶)
boolean[] hasNum = new boolean[length + 1];
// 用来存放每个桶中的最大值(length + 1 个桶)
int[] maxs = new int[length + 1];
// 用来存放每个桶中的最小值(length + 1 个桶)
int[] mins = new int[length + 1];
// 数组中的当前元素应该进入哪个桶
int bid;
// 更新当前桶中的最大值和最小值,标记是否是存入数据
for (int i = 0; i < length; i++) {
//确定当前数放入到哪个桶中
bid = bucket(arr[i], length, min, max);
// 如果当前桶中没有数,则当前数为最小,如果有则和原来最小数比较更新最小数
mins[bid] = hasNum[bid] ? Math.min(arr[i], mins[bid]) : arr[i];
// 如果当前桶中没有数,则当前数为最大,如果有则和原来最大数比较更新最大数
maxs[bid] = hasNum[bid] ? Math.min(arr[i], maxs[bid]) : arr[i];
hasNum[bid] = true;
}
// 全局最大差值
int result = 0;
// 第一个桶的最大值
int lastMax = maxs[0];
// 从第2个桶开始遍历,比较当前非空桶的最小值和上一个非空桶的最大值之间的差值和全局差值的大小关系
for (int i = 1; i < maxs.length; i++) {
if (hasNum[i]) {
result = Math.max(result, mins[i] - lastMax);
lastMax = maxs[i];
}
}
return result;
}
/**
* 确定数组中某个数该去几号桶
* @param num
* @param length
* @param min
* @param max
* @return
*/
private static int bucket(int num, int length, int min, int max) {
return((num - min) * length / (max - min));
}
01. 两数之和
/**
* @auther Mr.Liao
* @date 2019/11/5 14:26
* 给定一个整数数组 nums 和一个目标值 target
* 请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。
*/
public class Solution {
public static void main(String[] args) {
int[] arr = {2, 3, 7, 11};
System.out.println(Arrays.toString(twoSum3(arr,9)));
}
/**
* 一遍哈希表
* @param nums
* @param target
* @return
* 假设 a + b = target
*/
public static int[] twoSum2(int[] nums, int target) {
Map<Integer, Integer> map = new HashMap<>();
// 遍历数组,假设当前元素的为a,看是否存在b
for (int i = 0; i < nums.length; i++) {
// 当前元素a,对应的b
int complement = target - nums[i];
// b是否在数组中(是否存在map中),存在返回下标
if (map.containsKey(complement)) {
return new int[]{map.get(complement), i};
}
// 元素为k,下标为v,存入map中
map.put(nums[i], i);
}
throw new IllegalArgumentException("No two sum solution");
}
/**
* 不使用hash表 空间复杂度O(1)
* @param nums
* @param target
* @return
*/
public static int[] twoSum3(int[] nums, int target) {
// 先排序
Arrays.sort(nums);
int l = 0, r = nums.length - 1;
while (l < r) {
// 左右两个数相加大于target,右边界左移
if (nums[l] + nums[r] > target) {
r--;
} else if (nums[l] + nums[r] < target) {
// 小于,则左边界右移
l++;
} else {
// 找到相加等于的返回
return new int[]{l, r};
}
}
return new int[]{};
}
}
04. 有序数组的中位数
我们只需要直接找出在数组1切这一刀的正确位置就可以了。 为了减少查找次数,我们对短的数组进行二分查找。将在数组1切割的位置记为cut1,在数组2切割的位置记为cut2,cut2=(size/2)-cut1。
cut1,cut2分别表示的是数组1,数组2左边的元素的个数。
切这一刀的结果有三种
- 1)
L1 > R2
则cut1应该向左移,才能保证合并后的数组前后部分顺序正确
1 2 3 5 8 | 7 9 10 11 12 (此时结果是一样的,但是顺序不对,举了很多例子都会出现这种情况)
- 2)
L2 > R1
则cut1应该向右移,才能保证合并后的数组前后部分顺序正确
1 2 3 5 7 10 | 5 8 9 11 12 (此时顺序结果都不对)
- 3)其他情况(
L1 <= R2 L2 <= R1
),cut1的位置是正确的,可以停止查找,输出结果。
其他说明
1)考虑到边界条件,就是 cut 的位置可能在边缘,就是cut1=0或者cut1=N1,cut2=0或者cut2=N2的这些情况,我们将min和max两个特殊值分别加在数组1和数组2的两端,就可以统一考虑了。还有N1个数为0的时候,直接输出结果即可。
2)为了减少查找时间,使用的是二分查找,就是cut1的位置是一半一半的查找的,实现时间只要log(N),不然就会超时。所以,我们不能只是简单地将cut1--
或者cut1++
,而是要记下每次cut1的区域范围,我们将cut1的范围记录下来,用[cutL, cutR]
表示。一开始cut1的范围是[cutL,cutR]=[0,N1]
, 如果L1 > R2
则cut1应该向左移,cut1的范围就变成了[cutL, cut1-1]
,下次的cut1的位置就是cut1 = (cutR - cutL) / 2 + cutL;
如果L2 > R1
则cut1应该向右移,cut1的范围就变成了[cut1+1, cutR]
,下次的cut1的位置就是cut1 = (cutR - cutL) / 2 + cutL;
3)数组的元素个数和是奇数的情况下,中间的元素应该就是min(R1, R2),只需另外处理输出就可以了。
public static double findMedianSortedArrays(int[] nums1, int[] nums2) {
int N1 = nums1.length;
int N2 = nums2.length;
if (N1 > N2) {// 确保 nums1是短的部分。
return findMedianSortedArrays(nums2, nums1);
}
if (N1 == 0)
return ((double) nums2[(N2 - 1) / 2] + (double) nums2[N2 / 2]) / 2;
int N = N1 + N2;
int cutL = 0, cutR = N1;
int cut1 = 0, cut2;
while (cut1 <= N1) {
// cutL和 curR的中间位置
cut1 = (cutR - cutL) / 2 + cutL;
// cut2的位置
cut2 = N / 2 - cut1;
// cut1前面一个数
double L1 = (cut1 == 0) ? Integer.MIN_VALUE : nums1[cut1 - 1];
// cut2前面一个数
double L2 = (cut2 == 0) ? Integer.MIN_VALUE : nums2[cut2 - 1];
// R1位置就是cut1位置
double R1 = (cut1 == N1) ? Integer.MAX_VALUE : nums1[cut1];
// R2位置就是cut2位置
double R2 = (cut2 == N2) ? Integer.MAX_VALUE : nums2[cut2];
if (L1 > R2)
cutR = cut1 - 1; //下一次 cut1二分的范围,右边界往左
else if (L2 > R1)
cutL = cut1 + 1; // 下一次cut1二分的范围,左边界往右
else { // 找到正确的位置
if (N % 2 == 0) { // 偶数个数的时候,取四个数中间两个数
L1 = (L1 > L2 ? L1 : L2);
R1 = (R1 < R2 ? R1 : R2);
return (L1 + R1) / 2;
}
else { // 奇数个数的时候
R1 = (R1 < R2 ? R1 : R2);
return R1;
}
}
}
return -1;
}
拓展:找两个有序数组中,从小到大的第K个数
public class Solution {
public double findMedianSortedArrays(int nums1[], int nums2[]) {
int n = nums1.length + nums2.length;
// 两个数组长度加起来是偶数,找到中间两个数
if (n % 2 == 0) {
// 第(n / 2)个数
int a = findKth(nums1, 0, nums2, 0, n / 2);
// 第(n / 2 + 1)个数
int b = findKth(nums1, 0, nums2, 0, n / 2 + 1);
return (a + b) / 2.0;
}
// 两个数组长度加起来是奇数,找到第(n / 2 + 1)个数,就是中位数
return findKth(nums1, 0, nums2, 0, n / 2 + 1);
}
/**
* 两个数组中从小到大的第k个数
* @param A
* @param startOfA A数组开始下标,用来表示删掉以后哪些数还是有效的
* @param B
* @param startOfB B数组开始下标,用来表示删掉以后哪些数还是有效的
* @param k AB数组合起来C数组中的第几个数,注意不是下标
* @return
*/
public static int findKth(int[] A, int startOfA, int[] B, int startOfB, int k) {
// A数组空了
if (startOfA >= A.length) {
// 返回 B的第k个数
return B[startOfB + k - 1];
}
if (startOfB >= B.length) {
return A[startOfA + k - 1];
}
if (k == 1) {
return Math.min(A[startOfA], B[startOfB]);
}
// 找到A、B数组中从有效部分开始的第 k/2个数,对应数组中下标:有效位置+ k / 2 - 1
// 当某一个数组没有 k/2 个之后,用最大值替代
int halfKthOfA = startOfA + k / 2 - 1 < A.length ? A[startOfA + k / 2 - 1] : Integer.MAX_VALUE;
int halfKthOfB = startOfB + k / 2 - 1 < B.length ? B[startOfB + k / 2 - 1] : Integer.MAX_VALUE;
// 比较这两个数的大小
if (halfKthOfA < halfKthOfB) {
// 如果A的第 k/2个位置比B小,则丢掉A前 k/2个,要找的数变为第 k - k / 2 个,更新A起始位置
return findKth(A, startOfA + k / 2, B, startOfB, k - k / 2);
} else {
return findKth(A, startOfA, B, startOfB + k / 2, k - k / 2);
}
}
}
11. 盛最多水的容器
class Solution {
public static void main(String[] args) {
int[] arr = new int[]{1, 8, 6, 2, 5, 4, 8, 3, 7};
System.out.println(maxArea(arr));
}
public static int maxArea(int[] height) {
int area = 0, l = 0, r = height.length - 1;
while (l < r) {
area = height[l] < height[j] ?
Math.max(area, (r - l) * height[l++] :
Math.max(area, (r - l) * height[j--];
}
return area;
}
}
15. 三数之和
/**
* 标签:数组遍历,双指针
*
* 首先对数组进行排序,排序后固定一个数 nums[i],再使用左右指针指向 nums[i]后面的两端,
* 数字分别为 nums[L] 和 nums[R]计算三个数的和 sum 判断是否满足为 0,满足则添加进结果集
* 因为已经排好序了,当 nums[i]大于 0(后面的数都比它大),则三数之和必然无法等于 0,结束循环
* 如果 nums[i] == nums[i-1],则说明该数字重复,会导致结果重复,所以应该跳过
* 当 sum == 0 时,nums[L] == nums[L+1] 则会导致结果重复,应该跳过,L++
* 当 sum == 0 时,nums[R] == nums[R-1] 则会导致结果重复,应该跳过,R--
*
* 例如, 给定数组 nums = [-1, 0, 1, 2, -1, -4] 相加等于0
* 排序:[-4, -1, -1, 0, 1, 2]
* nums[i]=-4 nums[L]=-1 nums[R]=2
* [[-1, 0, 1],[-1, -1, 2]]
*/
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> arr = new ArrayList<>();
if (nums == null || nums.length < 3) {
return arr;
}
Arrays.sort(nums);
for (int i = 0; i < nums.length; i++) {
// l从1开始,r为数组结束下标
int l = i + 1, r = nums.length - 1;
// 如果两个数重复,跳过当次循环
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}
// 当前数大于0,不存在了
if (nums[i] > 0) {
break;
}
while (l < r) {
int sum = nums[i] + nums[l] + nums[r];
if (sum < 0) {
l++;
} else if (sum > 0) {
r--;
} else {
arr.add(Arrays.asList(nums[i], nums[l], nums[r]));
// 去重:l多移动一位
while (l < r && nums[l] == nums[l + 1]) {
l++;
}
// 去重:r多移动一位
while (l < r && nums[r] == nums[r - 1]) {
r--;
}
l++;
r--;
}
}
}
return arr;
}
}
26. 删除排序数组中多余重复元素
/**
* 删除重复项只保留一个
* 双指针
* 如果 l r指针指向的元素相同则 r继续右移
* 如果 l r指向的元素不同,则把 r位置的数换到 l的下一个位置,l r都右移一位
* 直到 r 指向数组结尾的下一个位置,此时l指向不重复新数组的最后一个位置
*/
class Solution {
public int removeDuplicates(int[] nums) {
if(nums == null || nums.length == 0) return 0;
int l = 0;
int r = 1;
while(r < nums.length){
// 把 r位置的数换到 l的下一个位置,l r都右移一位
if(nums[l] != nums[r]){
nums[l + 1] = nums[r];
l++;
}
r++;
}
return l + 1;
}
}
27. 删除数组中指定的元素
/**
* 全部移除某个元素
* 给定一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素,返回移除后数组的新长度。
* 不要使用额外的数组空间,你必须在原地修改输入数组并在使用 O(1) 额外空间的条件下完成。
* 元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
*/
class Solution {
public static void main(String[] args) {
int[] nums = {3, 2, 2, 3};
System.out.println(removeElement(nums, 3));
}
public static int removeElement(int[] nums, int val) {
int l = 0, r = 0;
while (r < nums.length) {
// 如果r位置的数不是给定的数,则把r位置的数赋值给l位置, l r都右移
if (nums[r] != val) {
nums[l] = nums[r];
l++;
}
// 如果是要删除的数,只是r右移
r++;
}
return l;
}
}
① 删除字符串中的空格
// 如果给的字符串数组,删除后返回不含空格的字符长度
public int deleteSpace(char[] str) {
int l = 0, r = 0;
while (r < str.length) {
if (str[r] != ' ') {
str[l] = str[r];
l++;
}
r++;
}
return l;
}
// 如果给的是字符串,则要使用额外空间
public String deleteSpace(String str) {
// 或者直接转成字符串数组,使用上面的方法
StringBuilder res = new StringBuilder();
for (int i = 0; i < str.length(); i++) {
char c = str.charAt(i);
if (c == ' ') {
continue;
}
res.append(c);
}
return res.toString();
}
31. 下一个全排列
class Solution {
public void nextPermutation(int[] nums) {
if (nums == null || nums.length == 0) return;
int n = nums.length;
int small = -1;
int big = 1;
// 从右到左,找到第一个左边比右边小的数
for (int i = n - 2; i >= 0; i--) {
if (nums[i] < nums[i + 1]) {
small = i;
break;
}
}
// 如果本来就是最大的(3,2,1),此时循环完后 small依然是 -1
if (small == -1) {
reverse(nums, 0, nums.length - 1);
return;
}
// 如果找到了一个比右边小的数,再从右到small位置找第一个比small大的数
for (int i = n - 1; i > small ; i--) {
if (nums[i] > nums[small]) {
big = i;
break;
}
}
swap(nums, small, big);
reverse(nums, small + 1, n - 1);
return;
}
private void swap(int[] nums, int i, int j) {
int temp = nums[i];
nums[i] = nums[j];
nums[j] = temp;
}
private void reverse(int[] nums, int i, int j) {
while (i < j) {
swap(nums, i, j);
i++;
j--;
}
}
}
42. 接雨水
class Solution {
public int trap(int[] height) {
if (height == null || height.length < 3) return 0;
int maxRain = 0, leftMax = 0, rightMax = 0, left = 0, right = height.length - 1;
while (left < right) {
// 更新左边最大值
leftMax = Math.max(leftMax, height[left]);
// 更新右边最大值
rightMax = Math.max(rightMax, height[right]);
if (leftMax < rightMax) {
maxRain += leftMax - height[left];
left++;
} else {
maxRain += rightMax - height[right];
right--;
}
}
return maxRain;
}
}
当左边最大值小于右边最大值的时候,左边当前墙能接的雨水是确定的
当左边最大值大于右边最大值的时候,右边当前墙能接的雨水是确定的
46. 全排列
class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1, 2, 3};
System.out.println(solution.permute(nums));
}
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length == 0) return res;
dfs(nums, res, new ArrayList<>(), new boolean[nums.length]);
return res;
}
/**
* @param nums
* @param res
* @param subset 以某一个数开头的结果 ([1,...]...[2,...]...[3,...])
* @param visited 用于记录当前递归中哪些数被访问了,在下一层递归中可以知道,从而跳过这个数
*/
private void dfs(int[] nums, List<List<Integer>> res, List<Integer> subset, boolean[] visited) {
// 退出条件:已经找完了数组中的所有数,即形成了一种可能,将该可能加入到结果中,返回
if (subset.size() == nums.length) {
res.add(new ArrayList<>(subset));
return;
}
// 拆解
for (int i = 0; i < nums.length; i++) {
// 当前元素已经被访问过,跳过当前元素
if (visited[i]) continue;
// 当前元素添加到当前层中
subset.add(nums[i]);
// 当前元素的状态设置为被访问过
visited[i] = true;
dfs(nums, res, subset, visited);
// 递归每一层返回的时候,将已经加入到subset中的数移除
subset.remove(subset.size() - 1);
// 并且清除访问状态
visited[i] = false;
}
}
}
47. 全排列 II
class Solution {
public static void main(String[] args) {
Solution solution = new Solution();
int[] nums = {1, 1, 3};
System.out.println(solution.permute(nums));
}
public List<List<Integer>> permute(int[] nums) {
List<List<Integer>> res = new ArrayList<>();
if (nums == null || nums.length == 0) return res;
Arrays.sort(nums);
dfs(nums, res, new ArrayList<>(), new boolean[nums.length]);
return res;
}
/**
* @param nums
* @param res
* @param subset 以某一个数开头的结果 ([1,...]...[2,...]...[3,...])
* @param visited 用于记录当前递归中哪些数被访问了,在下一层递归中可以知道,从而跳过这个数
*/
private void dfs(int[] nums, List<List<Integer>> res, List<Integer> subset, boolean[] visited) {
// 退出条件:已经找完了数组中的所有数,即形成了一种可能,将该可能加入到结果中,返回
if (subset.size() == nums.length) {
res.add(new ArrayList<>(subset));
return;
}
// 拆解
int preNum = nums[0] - 1;
for (int i = 0; i < nums.length; i++) {
// 当前数被访问过,或者当前数等于前一个数
if (visited[i] || nums[i] == preNum) continue;
preNum = nums[i];
// 当前元素添加到当前层中
subset.add(nums[i]);
// 当前元素的状态设置为被访问过
visited[i] = true;
dfs(nums, res, subset, visited);
// 递归每一层返回的时候,将已经加入到subset中的数移除
subset.remove(subset.size() - 1);
// 并且清除访问状态
visited[i] = false;
}
}
}
53. 最大子序和
方法一
public class Solution {
public static void main(String[] args) {
int[] arr = {-2, 1, -3, 4, -1, 2, 1, -5, 4};
System.out.println(maxSubArray2(arr));
}
public static int maxSubArray1(int[] nums) {
if (nums == null || nums.length == 0) {
return 0;
}
// max记录全局最大值,sum记录区间和,如果当前sum>0,那么可以继续和后面的数求和,否则就从0开始
int max = Integer.MIN_VALUE, sum = 0;
for (int i = 0; i < nums.length; i++) {
sum += nums[i];
max = Math.max(max, sum);
sum = Math.max(sum, 0);
}
return max;
}
}
方法二:分治
public static int maxSubArray3(int[] nums) {
return findMaxSubSum(nums, 0, nums.length - 1);
}
public static int findMaxSubSum(int[] nums, int start, int end) {
if (start == end) return nums[start];
if (start > end) return Integer.MIN_VALUE;
// 左边区间的最大子序和
int leftMax;
// 右边区间的最大子序和
int rightMax;
// 中间向左最大子序和
int middleToLeftMax = 0;
// 中间向右最大子序和
int middleToRightMax = 0;
int middle = start + (end - start) / 2;
leftMax = findMaxSubSum(nums, start, middle - 1);
rightMax = findMaxSubSum(nums, middle + 1, end);
// 从中间向左
for (int i = middle - 1, sum = 0; i >= start; i--) {
sum += nums[i];
if (middleToLeftMax < sum) middleToLeftMax = sum;
}
// 从中间向向右
for (int i = middle + 1, sum = 0; i <= end; i++) {
sum += nums[i];
if (middleToRightMax < sum) middleToRightMax = sum;
}
// 左右两个区间中较大的子序和,和跨过中点的最大子序和比较,取较大值
return Math.max(Math.max(leftMax, rightMax), middleToLeftMax + middleToRightMax + nums[middle]);
}
54. 螺旋矩阵
class Solution {
public static void main(String[] args) {
int[][] ints = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}, {10, 11, 12}};
spiralOrder(ints);
}
public static List<Integer> spiralOrder(int[][] matrix) {
List<Integer> res = new ArrayList<>();
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return res;
}
int left = 0, right = matrix[0].length - 1;
int top = 0, bottom = matrix.length - 1;
while (left < right && top < bottom) {
for (int i = left; i < right; i++) res.add(matrix[top][i]);
for (int i = top; i < bottom; i++) res.add(matrix[i][right]);
for (int i = right; i > left; i--) res.add(matrix[bottom][i]);
for (int i = bottom; i > top; i--) res.add(matrix[i][left]);
left++;
right--;
top++;
bottom--;
}
if (left == right) {
for (int i = top; i <=bottom ; i++) res.add(matrix[i][left]);
} else if (top == bottom) {
for (int i = left; i <=right ; i++) res.add(matrix[top][i]);
}
return res;
}
}
69. 螺旋矩阵2
参考54
class Solution {
public int[][] generateMatrix(int n) {
int left = 0, right = n - 1, top = 0, bottom = n - 1;
int[][] mat = new int[n][n];
int num = 1, tar = n * n;
while(top < bottom && left < right){
for(int i = left; i < right; i++) mat[top][i] = num++; // left to right.
for(int i = top; i < bottom; i++) mat[i][right] = num++; // top to bottom.
for(int i = right; i > left; i--) mat[bottom][i] = num++; // right to left.
for(int i = bottom; i > top; i--) mat[i][left] = num++; // bottom to top.
// 更新边界指针
top++;
bottom--;
left++;
right--;
}
// 当 n是奇数的时候,填上正中间这个数
if (n%2!=0) mat[n/2][n/2] = n;
return mat;
}
}
74. 搜索二维矩阵
/**
* 每行中的整数从左到右按升序排列。
* 每行的第一个整数大于前一行的最后一个整数。
* 1 2 3
* 4 5 6
* 7 8 9
*/
class Solution {
/**
* 一次二分
* @param matrix
* @param target
* @return
*/
public boolean searchMatrix1(int[][] matrix, int target) {
int m = matrix.length; // 行
if (m == 0) return false;
int n = matrix[0].length; // 列
// 二分查找,整体看成一个有序数组
int l = 0, r = m * n - 1;
int mid, x;
while (l <= r) {
mid = l + (r - l) / 2;
// mid位置的数在矩阵中的位置[mid / n][mid % n]
x = matrix[mid / n][mid % n];
if (target == x) return true;
else {
if (target < x) r = mid - 1;
else l = mid + 1;
}
}
return false;
}
/**
* 两次二分
* @param matrix
* @param target
* @return
*/
public boolean searchMatrix2(int[][] matrix, int target) {
if (matrix == null || matrix.length == 0) {
return false;
}
if (matrix[0] == null || matrix[0].length == 0) {
return false;
}
// 二分列,找到第一列中最后一个<=target的行,这个数可能就在这一行中
int row = matrix.length;
int column = matrix[0].length;
int start = 0, end = row - 1;
while (start + 1 < row) {
int mid = start + (end - start) / 2;
int x = matrix[mid][0];
if (x == target) {
return true;
} else if (x < target) {
start = mid;
} else {
end = mid;
}
}
if (matrix[end][0] <= target) {
row = end;
} else if (matrix[start][0] <= target) {
row = start;
} else {
return false;
}
// 二分行,找到列
start = 0;
end = column - 1;
while (start + 1 < end) {
int mid = start + (end - start) / 2;
int x = matrix[row][mid];
if (x == target) {
return true;
} else if (x < target) {
start = mid;
} else {
end = mid;
}
}
if (matrix[row][start] == target) {
return true;
} else if (matrix[row][end] == target) {
return true;
} else {
return false;
}
}
}
88. 合并两个有序数组
/**
* 初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
* 你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
*
* 因为 nums1 的空间都集中在后面,所以从后向前处理排序的数据会更好,节省空间,一边遍历一边将值填充进去
* 设置指针 len1 和 len2 分别指向 nums1 和 nums2 的有数字尾部,从尾部值开始比较遍历
* 同时设置指针 len 指向 nums1 的最末尾,每次遍历比较值大小之后,则进行填充
* 当 len1<0 时遍历结束,此时 nums2 中还有数据未拷贝完全,将其直接拷贝到 nums1 的前面,最后得到结果数组
* 时间复杂度:O(m+n)
*
*/
class Solution {
public void merge(int[] nums1, int m, int[] nums2, int n) {
// nums1有效数组的长度
int len1 = m - 1;
// nums2有效数组的长度
int len2 = n - 1;
// 合并后nums1从尾到头的位置
int len = m + n - 1;
while(len1 >= 0 && len2 >= 0) {
// 谁大谁往后填
nums1[len--] = nums1[len1] > nums2[len2] ? nums1[len1--] : nums2[len2--];
}
// 如果nums2中还有剩余的数,将剩余的数拷贝到nums1中,如果没有 len2=-1
System.arraycopy(nums2, 0, nums1, 0, len2 + 1);
}
}
121. 买卖股票的最佳时机1
/**
* 给定一个数组,它的第 i 个元素是一支给定股票第 i 天的价格。
* 如果你最多只允许完成一笔交易(即买入和卖出一支股票),设计一个算法来计算你所能获取的最大利润。
* 注意你不能在买入股票前卖出股票。
* 等价于:
* 在数组中找到两个数,使得后面的数减前面的数,差值最大,返回最大差值
*/
class Solution {
public static void main(String[] args) {
int[] arr = {7, 1, 5, 3, 6, 4};
System.out.println(maxProfit(arr));
}
public static int maxProfit(int[] prices) {
if (prices.length < 2) {
return 0;
}
int maxProfit = 0;
int minPrice = prices[0];
for (int i = 1; i < prices.length; i++) {
//找到价格最低的一天
minPrice = Math.min(minPrice, prices[i]);
//每一天都减去价格最低的一天,取最大值
maxProfit = Math.max(maxProfit, prices[i] - minPrice);
}
return maxProfit;
}
}
122. 买卖股票的最佳时机2
class Solution {
public int maxProfit(int[] prices) {
int maxProfit = 0;
// 从第二天开始计算
for (int i = 1; i < prices.length; i++) {
// 今天的价格减去昨天的价格,如果大于0,则获得利润
int temp = prices[i] - prices[i -1];
if (temp > 0) {
maxProfit += temp;
}
}
return maxProfit;
}
}
169. 多数元素(超过n/2)
class Solution {
public int majorityElement(int[] nums) {
int count = 0, candidate = -1;
for (int i = 0; i < nums.length; i++) {
// 当count为零时,当前数作为候选众数candidate
if (count == 0) {
candidate = nums[i];
count = 1;
} else if (candidate == nums[i]) {
// 后面的值和candidate相等,则count++
count++;
} else {
// 如果值不相等,则则count--,直到count为0,抵消掉之前的数
count--;
}
}
return candidate;
}
}
189. 旋转数组_和恢复
输入: [1,2,3,4,5,6,7] 和 k = 3
输出: [5,6,7,1,2,3,4]
解释:
向右旋转 1 步: [7,1,2,3,4,5,6]
向右旋转 2 步: [6,7,1,2,3,4,5]
向右旋转 3 步: [5,6,7,1,2,3,4]
class Solution {
public void rotate(int[] nums, int k) {
k %= nums.length;
// 1,2,3,4,5,6,7 -> 7,6,5,4,3,2,1
reverse(nums, 0, nums.length - 1);
// 7,6,5,4,3,2,1 -> 5,6,7,4,3,2,1
reverse(nums, 0, k - 1);
// 5,6,7,4,3,2,1 -> 5,6,7,1,2,3,4
reverse(nums, k, nums.length - 1);
}
/**
* 翻转数组
* @param nums
* @param start 起始下标
* @param end 结束下标
*/
public static void reverse(int[] nums, int start, int end) {
while (start < end) {
int temp = nums[start];
nums[start] = nums[end];
nums[end] = temp;
start++;
end--;
}
}
}
时间复杂度:O(n),n个元素被反转了总共 3 次。
空间复杂度:O(1),没有使用额外的空间。
200. 岛屿数量
class Solution {
int[] dx = {0, 1, 0, -1};
int[] dy = {1, 0, -1, 0};
public int numIslands(char[][] grid) {
if (grid == null || grid.length == 0 || grid[0].length == 0) return 0;
int cnt = 0;
int m = grid.length, n = grid[0].length;
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 如果是water就跳过,如果是land就recursion
if (grid[i][j] == '1') {
// 将成片的 land更新成 water
dfs(grid, i, j);
// 递归完之后,land的数量加一
cnt++;
}
}
}
return cnt;
}
private void dfs(char[][] grid, int i, int j) {
// 不越界,或者碰到了 water
if (i < 0 || i >= grid.length || j < 0 || j >= grid[0].length || grid[i][j] == '0') return;
// 把检查过的陆地更新成水,连成一片的 land算作一个岛屿
// 之所以要变成 water,表示已经计算过了,不影响后面的过程
grid[i][j] = '0';
// 然后再看当前元素的四个方向
for (int k = 0; k < 4; k++) {
dfs(grid, i + dx[k], j + dy[k]);
}
}
}
215. 数组中第k大元素
/**
* [3,2,3,1,2,4,5,5,6] 和 k = 4
* [6,5,5,4,3,3,2,2,1] 第4大,从左往右第4个就是4 两个5相等但是算两个
* 不是说 6第一个 5第二大 4第三大
*/
class Solution {
public static void main(String[] args) {
int[] nums = {3, 2, 3, 1, 2, 4, 5, 5, 6};
System.out.println(findKthLargest(nums, 4));
}
public static int findKthLargest(int[] nums, int k) {
PriorityQueue<Integer> queue = new PriorityQueue<>();
for (int num : nums) {
queue.offer(num);
System.out.println("添加元素:"+queue);
// 如果队列中的数量大于 k则弹出最小的,然后继续往里面放
if (queue.size() > k) queue.poll();
System.out.println("添加以后:"+queue);
}
// 循环完之后,队列中保留了 k个元素,最小的就是第 k大的元素
return queue.poll();
}
}
添加元素:[3]
添加以后:[3]
添加元素:[2, 3]
添加以后:[2, 3]
添加元素:[2, 3, 3]
添加以后:[2, 3, 3]
添加元素:[1, 2, 3, 3]
添加以后:[1, 2, 3, 3]
添加元素:[1, 2, 3, 3, 2]
添加以后:[2, 2, 3, 3]
添加元素:[2, 2, 3, 3, 4]
添加以后:[2, 3, 3, 4]
添加元素:[2, 3, 3, 4, 5]
添加以后:[3, 4, 3, 5]
添加元素:[3, 4, 3, 5, 5]
添加以后:[3, 4, 5, 5]
添加元素:[3, 4, 5, 5, 6]
添加以后:[4, 5, 5, 6]
4
229. 求众数 II(超过n/3次的元素)
class Solution {
public List<Integer> majorityElement(int[] nums) {
List<Integer> res = new ArrayList<>();
if (nums == null || nums.length == 0)
return res;
//初始化:定义两个候选人及其对应的票数
int candidateA = nums[0];
int candidateB = nums[0];
int countA = 0;
int countB = 0;
//遍历数组,找到出现次数最多的两个数,只有这两个数出现的次数可能大于n/3
for (int num : nums) {
if (num == candidateA) {
countA++;//投A
continue;
}
if (num == candidateB) {
countB++;//投B
continue;
}
//此时当前值和AB都不等,检查是否有票数减为0的情况,如果为0,则更新候选人
if (countA == 0) {
candidateA = num;
countA++;
continue;
}
if (countB == 0) {
candidateB = num;
countB++;
continue;
}
//若此时两个候选人的票数都不为0,且当前元素不投AB,那么A,B对应的票数都要--;
countA--;
countB--;
}
//上一轮遍历找出了两个候选人,但是这两个候选人是否均满足票数大于N/3仍然没法确定,需要重新遍历,确定票数
countA = 0;
countB = 0;
for (int num : nums) {
if (num == candidateA)
countA++;
else if (num == candidateB)
countB++;
}
if (countA > nums.length / 3)
res.add(candidateA);
if (countB > nums.length / 3)
res.add(candidateB);
return res;
}
}