1. 如何判断链表有环?
1.1 双重遍历
1.2 哈希表
1.3 追及问题
1.4 如果链表有环,如何求出环的长度?
1.5 如果链表有环,如何求出入环节点?
2. 最小栈的实现?
3. 如何求出最大公约数?
3.1 暴力枚举法
3.2 辗转相除法
3.3 更相减损术
3.4 基于移位的更相减损术
4. 如何判断一个数是否为2的整数次幂?
4.1 枚举
4.2 将乘替换乘移位
4.3 利用&
5. 无序数组排序后的最大相邻差?
5.1 排序遍历
5.2 利用计数排序的思想
5.3 利用桶排序的思想
6. 如何用栈实现队列?
7. 寻找全排列的下一个数?(字典序算法)
8. 删去k个数字后的最小值?(贪心算法)
8.1 常规思想解法
8.2 利用栈和外层循环内移提高效率
9. 如何实现大整数相加?
9.1 利用数组存储每一位
9.2 利用int的有效位数减少数组的长度
10. 如何求解金矿问题?(动态规划问题)
10.1 递归求解
10.2 自底向上求解
10.3 优化空间复杂度的自底向上求解
11. 寻找缺失的整数?
11.1 哈希表
11.2 排序
11.3 利用和作减法
11.4 问题扩展1
11.5 问题扩展2
首先从头结点开始,依次遍历单链表中的每一个节点,每遍历一个新节点,就从头检查新节点之前的所有节点,如果发现新节点和之前的某个节点相同,则说明该节点被遍历过两次,链表有环。
public class IsCycle {
private static class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
}
}
public static boolean isCycle(Node node) {
Node node1 = node.next;
int i = 1;
while (node1 != null) {
Node node2 = node;
int j = 0;
while (node2 != null && j < i) {
if (node2 == node1) {
return true;
}
j++;
node2 = node2.next;
}
i++;
node1 = node1.next;
}
return false;
}
public static void main(String[] args) {
Node node1 = new Node(5);
Node node2 = new Node(3);
node1.next = node2;
Node node3 = new Node(7);
node2.next = node3;
Node node4 = new Node(2);
node3.next = node4;
Node node5 = new Node(6);
node4.next = node5;
Node node6 = new Node(8);
node5.next = node6;
Node node7 = new Node(1);
node6.next = node7;
node7.next = node4;
System.out.println(isCycle(node1));
}
}
假设链表的节点数量为n,则方法1的时间复杂度为:O(n^2),空间复杂度为O(1)。
首先创建一个HashSet用来存储曾经遍历过的节点,每遍历一个节点,都用新节点同HashSet中存储的节点进行比较,如果相同,则说明链表有环。
public class IsCycle {
private static class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
}
}
public static boolean isCycle1(Node node) {
HashSet set = new HashSet<>();
Node node1 = node;
while (node1 != null) {
if (set.contains(node1)) {
return true;
}
set.add(node1);
node1 = node1.next;
}
return false;
}
public static void main(String[] args) {
Node node1 = new Node(5);
Node node2 = new Node(3);
node1.next = node2;
Node node3 = new Node(7);
node2.next = node3;
Node node4 = new Node(2);
node3.next = node4;
Node node5 = new Node(6);
node4.next = node5;
Node node6 = new Node(8);
node5.next = node6;
Node node7 = new Node(1);
node6.next = node7;
node7.next = node4;
System.out.println(isCycle1(node1));
}
}
假设链表的节点数量为n,则方法1的时间复杂度为:O(n),空间复杂度为O(n)。
首先创建两个对象引用,让它们同时指向这个链表的头节点,然后开始一个大循环,让引用1每次向后移动1个节点,引用2每次向后移动2个节点,然后比较两个引用指向的节点是否相同,如果相同,则说明链表有环。
类似于数学上的追及问题,因为是环形的,如果一个比另一个的速度快,那么它们肯定会相遇。
public class IsCycle {
private static class Node {
int data;
Node next;
public Node(int data) {
this.data = data;
}
}
public static boolean isCycle2(Node node) {
Node node1 = node;
Node node2 = node;
while (node2 != null && node2.next != null) {
node1 = node1.next;
node2 = node2.next.next;
if (node1 == node2) {
return true;
}
}
return false;
}
public static void main(String[] args) {
Node node1 = new Node(5);
Node node2 = new Node(3);
node1.next = node2;
Node node3 = new Node(7);
node2.next = node3;
Node node4 = new Node(2);
node3.next = node4;
Node node5 = new Node(6);
node4.next = node5;
Node node6 = new Node(8);
node5.next = node6;
Node node7 = new Node(1);
node6.next = node7;
node7.next = node4;
System.out.println(isCycle2(node1));
}
}
假设链表的节点数量为n,则方法1的时间复杂度为:O(n),空间复杂度为O(1)。
当两个引用首次相遇,证明链表有环的时候,让两个引用从相遇点继续前进,并统计前进的次数,直到两个引用第二次相遇,此时这个前进次数就是环的长度。
public static int getCycleLength(Node node) {
Node node1 = node;
Node node2 = node;
boolean firstMeet = false;
int length = 0;
while (node2 != null && node2.next != null) {
node1 = node1.next;
node2 = node2.next.next;
if (node1 == node2) {
if (!firstMeet) {
//第一次相遇
firstMeet = true;
} else {
//第二次相遇
return length;
}
}
if (firstMeet) {
length++;
}
}
return -1;
}
只需要记住:从链表头节点到入环点的距离,等于从首次相遇点回到入环点的距离。
所以,当两个引用首次相遇,让一个引用回到头节点继续前进,一个节点从相遇点继续前进,都每次只前进一个节点,直到两个引用第二次相遇,第二次相遇点就是入环点。
public static Node getInCycleNode(Node node) {
Node node1 = node;
Node node2 = node;
while (node2 != null && node2.next != null) {
node1 = node1.next;
node2 = node2.next.next;
if (node1 == node2) {
//第一次相遇
node1 = node;
break;
}
}
while (node1 != null && node2 != null) {
if (node1 == node2) {
//第二次相遇
return node1;
}
node1 = node1.next;
node2 = node2.next;
}
return null;
}
【问题】实现一个栈,该栈有出栈、入栈、取最小元素3个方法,要保证这3个方法的时间复杂度都是O(1)。
【思路】用两个栈来实现,栈A存所有元素,栈B存栈A当前的最小元素。
public class MinStack {
private static Stack mainStack = new Stack<>();
private static Stack minStack = new Stack<>();
public static void push(int element) {
mainStack.push(element);
if (minStack.isEmpty() || minStack.peek() >= element) {
minStack.push(element);
}
}
public static int pop() {
if (mainStack.peek().intValue() == minStack.peek()) {
minStack.pop();
}
return mainStack.pop();
}
public static int getMin() {
return minStack.peek();
}
public static void main(String[] args) {
MinStack.push(4);
MinStack.push(9);
MinStack.push(7);
MinStack.push(3);
MinStack.push(8);
MinStack.push(5);
System.out.println(MinStack.getMin());
MinStack.pop();
MinStack.pop();
MinStack.pop();
System.out.println(MinStack.getMin());
}
}
public class GreatestCommonDivisor {
public static int getGreatestCommonDivisor(int a, int b) {
int big = a > b ? a : b;
int small = a < b ? a : b;
if (big % small == 0) {
return small;
}
for (int i = small / 2; i > 1; i--) {
if (small % i == 0 && big % i == 0) {
return i;
}
}
return 1;
}
public static void main(String[] args) {
System.out.println(getGreatestCommonDivisor(25, 5));
System.out.println(getGreatestCommonDivisor(100, 80));
System.out.println(getGreatestCommonDivisor(27, 14));
}
}
该方法的问题是:如果传入的整数是10000和10001,就需要循环10000 / 2 - 1次,效率比较低。时间复杂度为O(min(a, b))。
两个正整数a和b(a > b),它们的最大公约数等于a % b和b的最大公约数。
public class GreatestCommonDivisor {
public static int getGreatestCommonDivisor1(int a, int b) {
int big = a > b ? a : b;
int small = a < b ? a : b;
if (big % small == 0) {
return small;
}
return getGreatestCommonDivisor1(big % small, small);
}
public static void main(String[] args) {
System.out.println(getGreatestCommonDivisor1(25, 5));
System.out.println(getGreatestCommonDivisor1(100, 80));
System.out.println(getGreatestCommonDivisor1(27, 14));
}
}
该方法的问题是:当两个整数比较大时,%的效率比较低。时间复杂度可以近似为O(log(max(a, b)))。
两个正整数a和b(a > b),它们的最大公约数等于a - b和b的最大公约数。
public class GreatestCommonDivisor {
public static int getGreatestCommonDivisor2(int a, int b) {
if (a == b) {
return a;
}
int big = a > b ? a : b;
int small = a < b ? a : b;
return getGreatestCommonDivisor1(big - small, small);
}
public static void main(String[] args) {
System.out.println(getGreatestCommonDivisor2(25, 5));
System.out.println(getGreatestCommonDivisor2(100, 80));
System.out.println(getGreatestCommonDivisor2(27, 14));
}
}
该方法的问题是:依靠差值来递归,递归的次数肯定远大于辗转相除法的次数。最坏时间复杂度为O(max(a, b))。
public class GreatestCommonDivisor {
public static int getGreatestCommonDivisor3(int a, int b) {
if (a == b) {
return a;
}
if ((a & 1) == 0 && (b & 1) == 0) {
//a和b均是偶数
return 2 * getGreatestCommonDivisor3(a >> 1, b >> 1);
} else if ((a & 1) == 0 && (b & 1) != 0) {
//a和偶数,b是奇数
return getGreatestCommonDivisor3(a >> 1, b);
} else if ((a & 1) != 0 && (b & 1) == 0) {
//a和奇数,b是偶数
return getGreatestCommonDivisor3(a, b >> 1);
} else {
//a和b均是奇数
int big = a > b ? a : b;
int small = a < b ? a : b;
return getGreatestCommonDivisor3(big - small, small);
}
}
public static void main(String[] args) {
System.out.println(getGreatestCommonDivisor2(25, 5));
System.out.println(getGreatestCommonDivisor2(100, 80));
System.out.println(getGreatestCommonDivisor2(27, 14));
}
}
时间复杂度为O(log(max(a, b)))。
利用一个整型变量,让它从1开始不断乘以2,将每一次乘2的结果和目标整数比较。
public class IsPowerOf2 {
public static boolean isPowerOf2(int num) {
int temp = 1;
while (temp <= num) {
if (temp == num) {
return true;
}
temp *= 2;
}
return false;
}
public static void main(String[] args) {
System.out.println(isPowerOf2(32));
System.out.println(isPowerOf2(19));
}
}
public class IsPowerOf2 {
public static boolean isPowerOf21(int num) {
int temp = 1;
while (temp <= num) {
if (temp == num) {
return true;
}
temp = temp << 1;
}
return false;
}
public static void main(String[] args) {
System.out.println(isPowerOf21(32));
System.out.println(isPowerOf21(19));
}
}
十进制 | 二进制 | 是否为2的整数次幂 |
8 | 1000B | 是 |
16 | 10000B | 是 |
32 | 100000B | 是 |
64 | 1000000B | 是 |
100 | 1100100B | 否 |
十进制 | 二进制 | 减一 | 是否为2的整数次幂 |
8 | 1000B | 111B | 是 |
16 | 10000B | 1111B | 是 |
32 | 100000B | 11111B | 是 |
64 | 1000000B | 111111B | 是 |
100 | 1100100B | 1100011B | 否 |
十进制 | 二进制 | 减一 | & | 是否为2的整数次幂 |
8 | 1000B | 111B | 0 | 是 |
16 | 10000B | 1111B | 0 | 是 |
32 | 100000B | 11111B | 0 | 是 |
64 | 1000000B | 111111B | 0 | 是 |
100 | 1100100B | 1100011B | 1100000B | 否 |
对于一个整数n,只需计算n & (n - 1)是否为0,就能判断它是否是2的整数次幂。
public class IsPowerOf2 {
public static boolean isPowerOf22(int num) {
return (num & (num - 1)) == 0;
}
public static void main(String[] args) {
System.out.println(isPowerOf22(32));
System.out.println(isPowerOf22(19));
}
}
【题目】比如无序数组:2 6 3 4 5 10 9,排序结果:2 3 4 5 6 9 10,最大相邻差为9 - 6 = 3。
使用任意一种时间复杂度为O(nlogn)的排序算法给原数组排序,然后遍历数组,对每两个相邻元素求差。
public class GetMaxSortedDistance {
public static int getMaxSortedDistance(int[] array) {
//对原数组进行快排
SortTest.quickSort(array, 0, array.length - 1);
//遍历排序后的数组,对相邻元素求差,取最大值
int max = 0;
for (int i = 0; i < array.length - 1; i++) {
int temp = Math.abs(array[i] - array[i + 1]);
if (max < temp) {
max = temp;
}
}
return max;
}
public static void main(String[] args) {
int[] array = new int[]{2, 6, 3, 4, 5, 10, 9};
System.out.println(getMaxSortedDistance(array));
}
}
利用计数排序的思想,问题就是,如果数值比较大,需要创建很长的数组。
public class GetMaxSortedDistance {
public static int getMaxSortedDistance1(int[] array) {
//得到序列的最大值和最小值,并计算差值d
int max = array[0];
int min = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
int d = max - min;
//如果差值为0,则说明所有元素都相等
if (d == 0) {
return 0;
}
//创建统计数组并统计对应元素的个数
int[] countArray = new int[d + 1];
for (int i = 0; i < array.length; i++) {
countArray[array[i] - min]++;
}
//遍历统计数组,统计出最大连续出现0值的次数加1,即为相邻元素最大差值
boolean start = countArray[0] == 0;
int maxDistance = 0;
int count = 0;
for (int i = 0; i < countArray.length - 1; i++) {
if (countArray[i + 1] == 0) {
count++;
if (!start) {
start = true;
}
} else {
if (start) {
if (maxDistance < count) {
maxDistance = count;
count = 0;
start = false;
}
}
}
}
return maxDistance + 1;
}
public static void main(String[] args) {
int[] array = new int[]{2, 6, 3, 4, 5, 10, 9};
System.out.println(getMaxSortedDistance1(array));
}
}
利用桶排序的思想,时间复杂度稳定在O(n)。
public class GetMaxSortedDistance {
private static class Bucket {
Integer max;
Integer min;
}
public static int getMaxSortedDistance2(int[] array) {
//得到序列的最大值和最小值,并计算差值d
double max = array[0];
double min = array[0];
for (int i = 1; i < array.length; i++) {
if (array[i] > max) {
max = array[i];
}
if (array[i] < min) {
min = array[i];
}
}
double d = max - min;
//如果差值为0,则说明所有元素都相等
if (d == 0) {
return 0;
}
//初始化桶
int bucketNum = array.length;
Bucket[] buckets = new Bucket[bucketNum];
for (int i = 0; i < buckets.length; i++) {
buckets[i] = new Bucket();
}
//遍历原数组,确定每个桶的最大值和最小值
for (int i = 0; i < array.length; i++) {
int num = (int) ((array[i] - min) * (bucketNum - 1) / d);
if (buckets[num].min == null || buckets[num].min > array[i]) {
buckets[num].min = array[i];
}
if (buckets[num].max == null || buckets[num].max < array[i]) {
buckets[num].max = array[i];
}
}
//遍历桶,统计每一个桶的最大值和这个桶右侧非空桶的最小值的差,差值最大的就是结果
int leftMax = buckets[0].max;
int maxDistance = 0;
for (int i = 1; i < buckets.length; i++) {
if (buckets[i].min == null) {
continue;
}
if (buckets[i].min - leftMax > maxDistance) {
maxDistance = buckets[i].min - leftMax;
}
leftMax = buckets[i].max;
}
return maxDistance;
}
public static void main(String[] args) {
int[] array = new int[]{2, 6, 3, 4, 5, 10, 9};
System.out.println(getMaxSortedDistance2(array));
}
}
public class QueueByStack {
private static Stack stack1 = new Stack<>();
private static Stack stack2 = new Stack<>();
public static void enQueue(int element) {
stack1.push(element);
}
public static int deQueue() {
if (stack2.isEmpty()) {
//栈1的元素出栈、入栈到栈2
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public static void main(String[] args) {
QueueByStack.enQueue(1);
QueueByStack.enQueue(2);
QueueByStack.enQueue(3);
System.out.println(QueueByStack.deQueue());
System.out.println(QueueByStack.deQueue());
QueueByStack.enQueue(4);
System.out.println(QueueByStack.deQueue());
System.out.println(QueueByStack.deQueue());
}
}
入队操作的时间复杂度是O(1),如果涉及元素迁移,那么出队操作的时间复杂度是O(n),如果不用迁移,出队操作的时间复杂度是O(1),所以可以均摊时间复杂度,需要元素迁移的出队操作不可能连续出现,所以可以把时间均摊到每一次出队操作上面,所以出队操作的时间复杂度是O(1)。
【题目】如果输入12345,则返回12354,;输入12354,则返回12435;输入12435,则返回12453。
【思路】如果输入12354,54是逆序的,只有改变逆序区间的前一个数才能让它更大,所以将3与4交换,得到12453,再将53顺序,即得到了比12354只大一点的全排列数。
public class FindNearestNumber {
public static int[] findNearestNumber(int[] numbers) {
//从右往左找到逆序区域的边界
int i = numbers.length - 1;
for (; i > 0; i--) {
if (numbers[i] > numbers[i - 1]) {
break;
}
}
//表示完全逆序
if (i == 0) {
return null;
}
//当前从i到numbers.length - 1都是逆序的,将其中刚好比numbers[i - 1]大的那个数与之替换
for (int j = numbers.length - 1; j > i - 1; j--) {
if (numbers[j] > numbers[i - 1]) {
int temp = numbers[i - 1];
numbers[i - 1] = numbers[j];
numbers[j] = temp;
break;
}
}
//当前从i到numbers.length - 1还是逆序的,将这区间顺序
for (int j = i, k = numbers.length - 1; j <= k; j++, k--) {
int temp = numbers[j];
numbers[j] = numbers[k];
numbers[k] = temp;
}
return numbers;
}
public static void main(String[] args) {
int[] numbers = {1, 2, 3, 5, 4};
for (int i = 0; i < 10 && numbers != null; i++) {
numbers = findNearestNumber(numbers);
System.out.println(Arrays.toString(numbers));
}
}
}
【题目】给出一个整数,从该整数中去掉k个数字,要求剩下的数字形成的新整数尽可能小。
【思路】把原整数从左到右进行比较,如果发现某一位数字大于它右边的数字,那么删除该数字后,必然会使得该数位的值降低。
public class RemoveKDigits {
public static String removeKDigits(String num, int k) {
while (k-- > 0) {
boolean hasCut = false;
for (int i = 0; i < num.length() - 1; i++) {
if (num.charAt(i) > num.charAt(i + 1)) {
num = num.substring(0, i) + num.substring(i + 1);
hasCut = true;
break;
}
}
if (!hasCut && num.length() != 1) {
num = num.substring(0, num.length() - 1);
}
}
return Long.valueOf(num.length() == 0 ? "0" : num).toString();
}
public static void main(String[] args) {
System.out.println(removeKDigits("1593212", 3));
System.out.println(removeKDigits("30200", 1));
System.out.println(removeKDigits("10", 2));
System.out.println(removeKDigits("541270936", 3));
System.out.println(removeKDigits("123", 1));
}
}
该方法存在两个问题:
public class RemoveKDigits {
public static String removeKDigits1(String num, int k) {
int newLength = num.length() - k;
//模拟栈
char[] stack = new char[num.length()];
int stackTop = -1;
for (int i = 0; i < num.length(); i++) {
char c = num.charAt(i);
while (stackTop > -1 && stack[stackTop] > c && k > 0) {
//模拟出栈
stackTop--;
k--;
}
//模拟入栈
stack[++stackTop] = c;
}
//去掉开头的0
int notZeroIndex = 0;
while (notZeroIndex < newLength && stack[notZeroIndex] == '0') {
notZeroIndex++;
}
return notZeroIndex == newLength ? "0" : new String(stack, notZeroIndex, newLength);
}
public static void main(String[] args) {
System.out.println(removeKDigits1("1593212", 3));
System.out.println(removeKDigits1("30200", 1));
System.out.println(removeKDigits1("10", 2));
System.out.println(removeKDigits1("541270936", 3));
System.out.println(removeKDigits1("123", 1));
}
}
像这样依次求得局部最优解,最终得到全局最优解的思想,叫做贪心算法。
【思路】利用数组,让数组的每一位相加,该进位进位。
public class BigNumberSun {
public static String bigNumberSum(String bigNumberA, String bigNumberB) {
String bigger = bigNumberA.length() > bigNumberB.length() ? bigNumberA : bigNumberB;
int[] numberA = new int[bigger.length() + 1];
int[] numberB = new int[bigger.length() + 1];
int[] sumAB = new int[bigger.length() + 1];
for (int i = 0; i < bigger.length() + 1; i++) {
if (bigNumberA.length() - 1 - i > -1) {
numberA[i] = bigNumberA.charAt(bigNumberA.length() - 1 - i) - '0';
} else {
numberA[i] = 0;
}
if (bigNumberB.length() - 1 - i > -1) {
numberB[i] = bigNumberB.charAt(bigNumberB.length() - 1 - i) - '0';
} else {
numberB[i] = 0;
}
}
for (int i = 0; i < sumAB.length; i++) {
int temp = numberA[i] + numberB[i] + sumAB[i];
if (temp > 9) {
sumAB[i] = temp - 10;
sumAB[i + 1] = 1;
} else {
sumAB[i] = temp;
}
}
StringBuilder sb = new StringBuilder();
boolean zero = true;
for (int i = sumAB.length - 1; i > -1; i--) {
if (zero) {
if (sumAB[i] == 0) {
continue;
}
zero = false;
}
sb.append(sumAB[i]);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(bigNumberSum("426709752318", "95481253129"));
}
}
该方法中用到3个数组,数组长度是最大整数长度加1(为了进位),进一步优化就是减少数组的长度。
public class BigNumberSun {
public static String bigNumberSum1(String bigNumberA, String bigNumberB) {
String bigger = bigNumberA.length() > bigNumberB.length() ? bigNumberA : bigNumberB;
int[] numberA = new int[bigger.length() / 9 + 1];
int[] numberB = new int[bigger.length() / 9 + 1];
int[] sumAB = new int[bigger.length() / 9 + 1];
int start = 0;
int end = bigNumberA.length() % 9;
for (int i = 0; i < bigNumberA.length() / 9 + 1; i++) {
numberA[i] = Integer.valueOf(bigNumberA.substring(start, end));
start = end;
end = end + 9;
}
start = 0;
end = bigNumberB.length() % 9;
for (int i = 0; i < bigNumberB.length() / 9 + 1; i++) {
numberB[i] = Integer.valueOf(bigNumberB.substring(start, end));
start = end;
end = end + 9;
}
for (int i = sumAB.length - 1; i > -1; i--) {
int temp = numberA[i] + numberB[i] + sumAB[i];
if (String.valueOf(temp).length() > String.valueOf(numberA[i]).length()) {
sumAB[i] = Integer.valueOf(String.valueOf(temp).substring(1));
sumAB[i - 1] = 1;
} else {
sumAB[i] = temp;
}
}
StringBuilder sb = new StringBuilder();
for (int i = 0; i < sumAB.length; i++) {
sb.append(sumAB[i]);
}
return sb.toString();
}
public static void main(String[] args) {
System.out.println(bigNumberSum1("426709752318", "95481253129"));
}
}
由于int的范围最多可以有10位整数,所以可以把大整数的每9位作为数组的一个元素,进行加法运算,这也是Java中BigInteger类等的底层实现思路。
【题目】假设现在有5个金矿,分别是:200KG黄金需要3人开采、300KG黄金需要4人开采、350KG黄金需要3人开采、400KG黄金需要5人开采、500KG黄金需要5人开采,总共有10人,问怎样开采能取得最大受益。
【思路】对于最后一个金矿(500KG黄金需要5人开采),我们有2种选择,要开采或者不开采:
一直这样递归下去,直到0个金矿或0人开采,受益返回为0。
public class GetBestGoldMining {
public static int getBestGoldMining(int w, int n, int[] p, int[] g) {
//递归退出条件,当工人数为0或金矿数为0
if (w == 0 || n == 0) {
return 0;
}
//如果一定要开采最后一个金矿,那么工人数必定要大于开采该金矿所需人数,在要开采最后一个金矿和不开采最后一个金矿中取最大收益
//否则的话,最能采取不开采最后一个金矿
if (w < p[n - 1]) {
return getBestGoldMining(w, n - 1, p, g);
}
return Math.max(
getBestGoldMining(w, n - 1, p, g),
getBestGoldMining(w - p[n - 1], n - 1, p, g) + g[n - 1]
);
}
public static void main(String[] args) {
int w = 10;
int[] p = {5, 5, 3, 4, 3};
int[] g = {400, 500, 200, 300, 350};
System.out.println(getBestGoldMining(w, g.length, p, g));
}
}
该方法采用递归,如果金矿数量是n,人数充足,时间复杂度能达到O(2^n),当金矿很多的时候,效率非常低,原因是递归中做了很多重复的计算。
问题的求解可以演变为这样一张表格:
1个工人 | 2个工人 | 3个工人 | 4个工人 | 5个工人 | 6个工人 | 7个工人 | 8个工人 | 9个工人 | 10个工人 | |
400KG黄金/5人 | 0 | 0 | 0 | 0 | 400 | 400 | 400 | 400 | 400 | 400 |
500KG黄金/5人 | 0 | 0 | 0 | 0 | 500 | 500 | 500 | 500 | 500 | 900 |
200KG黄金/3人 | ||||||||||
300KG黄金/4人 | ||||||||||
350KG黄金/3人 |
表格中第i行第j列的含义就是:i个金矿j人进行开采的最大收益。
比如表格中的红色900,表示的是2个金矿10个人进行开采的最大受益为900,它是通过它的2个子情况来的:
public class GetBestGoldMining {
public static int getBestGoldMining1(int w, int n, int[] p, int[] g) {
//创建表格
int[][] resultTable = new int[g.length + 1][w + 1];
//填充表格
//resultTable[i][j]代表i个金矿、j个工人时的最大收益
for (int i = 1; i < resultTable.length; i++) {
for (int j = 1; j < resultTable[0].length; j++) {
if (j < p[i - 1]) {
resultTable[i][j] = resultTable[i - 1][j];
} else {
resultTable[i][j] = Math.max(
resultTable[i - 1][j],
resultTable[i - 1][j - p[i - 1]] + g[i - 1]
);
}
}
}
//返回最后一个格子的值
return resultTable[g.length][w];
}
public static void main(String[] args) {
int w = 10;
int[] p = {5, 5, 3, 4, 3};
int[] g = {400, 500, 200, 300, 350};
System.out.println(getBestGoldMining1(w, g.length, p, g));
}
}
该方法的时间复杂度和空间复杂的都是O(wn),还可以进一步优化空间复杂度。
通过10.2的分析可以知道,每一行的结果都是由上一行的数据推导出来的,那么就不需要保存整个表格,在计算新数据的时候,只需要把以前的数据替换掉就行了,但是要从右向左进行统计。
public class GetBestGoldMining {
public static int getBestGoldMining2(int w, int n, int[] p, int[] g) {
//创建当前结果表格
int[] results = new int[w + 1];
//填充表格
for (int i = 1; i < g.length + 1; i++) {
for (int j = w; j > 0; j--) {
if (j >= p[i - 1]) {
results[j] = Math.max(results[j], results[j - p[i - 1]] + g[i - 1]);
}
}
}
//返回最后一个格子的值
return results[w];
}
public static void main(String[] args) {
int w = 10;
int[] p = {5, 5, 3, 4, 3};
int[] g = {400, 500, 200, 300, 350};
System.out.println(getBestGoldMining2(w, g.length, p, g));
}
}
优化后的空间复杂度是O(w)。
【题目】在一个无序数组里有9个不重复的正整数,范围是1~10,唯独缺少1个1~10种的整数,如何找出这个数?
【思路】用一个哈希表,以1~10作为key,然后遍历该数组,根据每一个值删除对应的key,最终剩下的key就是缺少的数。
public class FindLostNum {
public static int findLostNum(int[] array) {
//初始化哈希表
Map map = new HashMap<>();
for (int i = 1; i < array.length + 1; i++) {
map.put(i, null);
}
//从哈希表中删除已经存在的数
for (int i = 0; i < array.length; i++) {
map.remove(array[i]);
}
//剩下的就是缺少的数
for (Map.Entry entry : map.entrySet()) {
return entry.getKey();
}
return -1;
}
public static void main(String[] args) {
int[] array = {4, 1, 3, 2, 5, 7, 8, 9, 10};
System.out.println(findLostNum(array));
}
}
假设数组长度为n,该方法的时间按复杂度是O(n),空间复杂度也是O(n)。
【思路】先使用O(nlogn)的排序方法将原数组排序,然后遍历排序后的数组,如果相邻元素不连续,那么就找到缺少的数。
public class FindLostNum {
public static int findLostNum1(int[] array) {
//快排
SortTest.quickSort(array, 0, array.length - 1);
//遍历排序后的数组,如果相邻元素不连续,说明找到缺少的数
for (int i = 0; i < array.length - 1; i++) {
if (array[i] + 1 != array[i + 1]) {
return array[i] + 1;
}
}
return -1;
}
public static void main(String[] args) {
int[] array = {4, 1, 3, 2, 5, 7, 8, 9, 10};
System.out.println(findLostNum1(array));
}
}
假设数组长度为n,该方法的时间按复杂度是O(nlogn),空间复杂度是O(1)。
【思路】求1~10的和,用这个和依次减去数组中元素,剩下的就是缺少的数。
public class FindLostNum {
public static int findLostNum2(int[] array) {
//计算没有缺少数时所有数之和
int sum = 0;
for (int i = 1; i < array.length + 2; i++) {
sum += i;
}
//依次减去已经存在的数
for (int i = 0; i < array.length; i++) {
sum -= array[i];
}
//剩下的就是缺少的数
return sum;
}
public static void main(String[] args) {
int[] array = {4, 1, 3, 2, 5, 7, 8, 9, 10};
System.out.println(findLostNum2(array));
}
}
假设数组长度为n,该方法的时间按复杂度是O(n),空间复杂度是O(1)。
【问题】一个无序数组里有若干个正整数,范围是1~100,其中99个整数都出现了偶数次,只有1个整数出现了奇数次,如何找到这个数?
【思路】利用异或,异或在进行运算时,相同为0,不相同为1,因此出现偶数次的数都会被抵消为0,只有出现奇数次的数会留下。
public class FindLostNum {
public static int findLostNum3(int[] array) {
int xorResult = 0;
for (int i = 0; i < array.length; i++) {
xorResult ^= array[i];
}
return xorResult;
}
public static void main(String[] args) {
int[] array = {4, 1, 2, 2, 5, 1, 4};
System.out.println(findLostNum3(array));
}
}
【问题】一个无序数组里有若干个正整数,范围是1~100,其中98个整数都出现了偶数次,有2个整数出现了奇数次,如何找到这2个数?
【思路】在进行11.4的操作后,异或结果是2个出现了奇数次的数的异或结果。假设两个数是A和B,异或结果是C,如果C的某一位为1,那么就可以说明A和B在这一位上是不等的,相等的话就为0了。所以可以根据这一位将数组中的所有数区分到两个阵营,一个阵营是这一位为0,一个阵营是这一位为1。再对两个阵营分别进行11.4的操作,就得出最终结果。
public class FindLostNum {
public static int[] findLostNum4(int[] array) {
int[] result = new int[2];
int xorResult = 0;
for (int i = 0; i < array.length; i++) {
xorResult ^= array[i];
}
if (xorResult == 0) {
return null;
}
//找到不同的位,以此来作分组
int separator = 1;
while (0 == (xorResult & separator)) {
separator <<= 1;
}
for (int i = 0; i < array.length; i++) {
if (0 == (array[i] & separator)) {
result[0] ^= array[i];
} else {
result[1] ^= array[i];
}
}
return result;
}
public static void main(String[] args) {
int[] array = {4, 1, 2, 2, 5, 1, 4, 3};
System.out.println(findLostNum4(array));
}
}
这两个问题扩展的方法的时间复杂度度都是O(n),空间复杂度都是O(1)。