延伸出下列问题:
值的你关注并提升你薪资待遇的面试算法:开源数据结构和算法实践
问题1解答
问题2解答
问题3解答
回到题目本身:给定不同面额的硬币 array 和一个总金额 target。求出组成target金额的硬币序列。其中一种情况是:{1,2,2},但如果考虑排列问题,{1,2,2}、{2,1,2}、{2,2,1} 属于三个不同的答案。
public void roll(int depth, int[] array, int target) {
if (sum == target) {
list_all.add(new ArrayList<>(list_temp));
return;
}
if (sum > target) {
return;
}
// i 从0开始,是排列问题,从上次的深度depth开始,是组合问题
for (int i = depth; i < array.length; i++) {
list_temp.add(array[i]);
sum += array[i];
roll(i, array, target);
sum -= array[i];
list_temp.remove(list_temp.size() - 1);
}
}
3.1 提到的是组合和排列问题使用递归方式的解法,那么动态规划是否也可以做到呢?对于题目要求的给定不同面额的硬币 array 和一个总金额 target。求出组成target金额的硬币序列的个数。
for (int i = 0; i <= target; i++) {
for (int coin : coins) {
//VIPTips:VIPTips:两层for循环顺序的差异是:coins在外面是求解排列数,coins在里面是是求解组合数
if (i >= coin) {
dp[i] += dp[i - coin];
}
}
}
问题4解答
零钱数组每个数仅能用1次的基础上,是否还能凑出target表示金额
问题5解答
当组合问题存在,属于同一组合不同排列的问题就会比较烧脑。即为:存在相同数字,比如 [1,2,2’],在排列的过程中存在答案 [1,2,2’] 和 [1,2’,2] 是一样的。但是[1,2,2’]和[2,1,2’]不一样。
public void roll(int depth, int[] nums) {
if (depth == nums.length) {
list_temp = new ArrayList<>();
String checkString = ArrayUtilsImpl.IntArray2Sequence(nums);
// 通过map去重
if (!containsMap.containsKey(checkString)) {
containsMap.put(checkString, 1);
for (int i = 0; i < nums.length; i++) {
list_temp.add(nums[i]);
}
list_all.add(new ArrayList<>(list_temp));
}
}
for (int i = depth; i < nums.length; i++) {
int temp = nums[i];
nums[i] = nums[depth];
nums[depth] = temp;
roll(depth + 1, nums);
temp = nums[i];
nums[i] = nums[depth];
nums[depth] = temp;
}
}
public void roll(int depth, int[] nums) {
if (depth == nums.length) {
list_temp = new ArrayList<>();
for (int i = 0; i < nums.length; i++) {
list_temp.add(nums[i]);
}
list_all.add(new ArrayList<>(list_temp));
return;
}
// 每一层放置一个 existMap,用于统计在当前层,是否访问过 nums[i]
Map existMap = new HashMap();
for (int i = depth; i < nums.length; i++) {
if (existMap.containsKey(nums[i])) {
continue;
}
existMap.put(nums[i], true);
int temp = nums[i];
nums[i] = nums[depth];
nums[depth] = temp;
roll(depth + 1, nums);
temp = nums[i];
nums[i] = nums[depth];
nums[depth] = temp;
}
}
问题6解答
给定一个钱币数组,限制随意组合,仅能选取连续的钱币,那么是否还能凑出target表示的金额
Map map = new HashMap();
int[] prefixSumArray = new int[array.length];
map.put(array[0], true);
prefixSumArray[0] = array[0];
for (int i = 1; i < array.length; i++) {
prefixSumArray[i] = prefixSumArray[i - 1] + array[i];
map.put(prefixSumArray[i], true);
int wantNum = prefixSumArray[i] - target;
if (map.getOrDefault(wantNum, false)) {
return true;
}
}
给定不同面额的硬币 array 和一个总金额 target,如果限制随意组合,仅能选取间隔的钱币,那么是否还能凑出target表示金额
private boolean roll(int depth, int[] array, int target) {
// 超过长度 或者 超过预期
if (depth >= array.length || array[depth] + sum > target) {
return false;
}
sum += array[depth];
if (sum == target) {
return true;
}
for (int i = depth + 2; i < array.length; i++) {
boolean flag = roll(i, array, target);
if (flag) {
return flag;
}
}
sum -= array[depth];
return false;
}
问题7解答
public boolean roll(int depth, int[] array, int target) {
if (depth == array.length) {
return false;
}
if (multiSum == target || array[depth] == target) {
return true;
}
for (int i = 0; i < array.length; i++) {
if (array[i] == 0) {
continue;
}
multiSum *= array[i];
flag = roll(depth + 1, array, target);
if (flag) {
return true;
}
multiSum /= array[i];
}
return false;
}
while (right < array.length || left < right) {
// 右指针扩张
while (right < array.length && mul * array[right] <= target) {
mul *= array[right];
right++;
}
roll(array, left, right - 1);
// 左指针收缩
mul /= array[left];
left++;
}
问题8解答
将 target 拆分为至少两个正整数的和,并使这些整数的乘积最大化
for (int i = 2; i <= num; i++) {
for (int j = 1; j < i; j++) {
maxMultiarray[i] = Math.max(maxMultiarray[i], (i - j) * Math.max(j, maxMultiarray[j]));
}
}
问题9解答:子段和/积包括哪些问题
1和2是乘积问题,区别在于是否连续,3和4是求和问题,区别在于是否连续。
for (int i = 2; i < length; i++) {
valueMax[i] = Math.max(Math.max(Math.max(
valueMax[i - 2] * values[i], valueMin[i - 2] * values[i]), //选择间隔积
valueMax[i - 1]),//选择上一个最优解
values[i]); //选择当前值
valueMin[i] = Math.min(Math.min(Math.min(
valueMax[i - 2] * values[i], valueMin[i - 2] * values[i]), //选择间隔积
valueMin[i - 1]),//选择上一个最优解
values[i]); //选择当前值
}
public void roll(int depth, int[] array) {
for (int i = depth; i < array.length; i++) {
if (array[i] == 0) {
if (0 > best) {
best = 0;
}
return;
}
sum *= array[i];
list_temp.add(array[i]);
if (sum > best) {
list_best = new ArrayList(list_temp);
best = sum;
}
}
}
for (int i = 1; i < array.length; ++i) {
long max_old = max, min_old = min;
// Tips: Math.max(max_old * array[i], min_old * array[i]) 不等于 Math.max(max_old, min_old) * array[i]
max = Math.max(array[i], Math.max(max_old * array[i], min_old * array[i]));
min = Math.min(array[i], Math.min(max_old * array[i], min_old * array[i]));
answer = Math.max(max, answer);
}
给定一个数组,在这个数组中,进行非连续的选择,即挑选任意非相邻的数字组成的数组,求这些数组中和值最大的值。
bestGoodsValue[i] = Math.max(
bestGoodsValue[i - 1], //不选择当前的物品
Math.max(bestGoodsValue[i - 2] + array[i], array[i])//选择当前的物品
);
for (int i = 2; i < length; i++) {
bestGoodsValue[i] = Math.max(
bestGoodsValue[i - 1], //不选择当前的物品
Math.max(bestGoodsValue[i - 2] + values[i], values[i])//选择当前的物品
);
}
for (int i = depth; i < array.length; i++) {
sum += array[i];
list_temp.add(array[i]);
// tips: 此处的 i 或者 depth 需要注意,常规情况下都是使用i+1,只有在对数组做全排列才会考虑使用depth。
roll(i + 2, array);
list_temp.remove(list_temp.size() - 1);
sum -= array[i];
}
给定一个数组,求这个数组的连续子数组中,最大的那一段的和。
for (int i = 1; i < length; i++) {
LargestSum[i] = Math.max(LargestSum[i - 1] + array[i], array[i]);
if (LargestSum[i] > sum) {
sum = LargestSum[i];
}
}
int leftValue = divide(Sequence, left, mid);
int rightValue = divide(Sequence, mid + 1, right);
int midValue = mid(Sequence, left, right);
return Math.max(Math.max(leftValue, rightValue), midValue);
问题10解答
public void count(BinaryTreeImpl node) {
if (node == null) {
return;
}
count(node.left);
count(node.right);
// containMap需要把 node.value 考虑进去
containMap.put(node, node.value + nonContainMap.getOrDefault(node.left, 0) + nonContainMap.getOrDefault(node.right, 0));
nonContainMap.put(node, Math.max(containMap.getOrDefault(node.left, 0), nonContainMap.getOrDefault(node.left, 0))
+ Math.max(containMap.getOrDefault(node.right, 0), nonContainMap.getOrDefault(node.right, 0)));
}
问题11解答
给定不同面额的硬币 coins 和一个总金额 target,如果零钱数组加上数量限制数组 limit,即每个零钱有一个限定使用的最大值,那么是否还能凑出target
private boolean roll(int depth, int[] array, int[] limit, int target) {
if (sumTemp == target) {
return true;
}
if (sumTemp > target || depth == array.length) {
return false;
}
for (int i = 0; i <= limit[depth]; i++) {
sumTemp += array[depth] * i;
boolean flag = roll(depth + 1, array, limit, target);
if (flag) {
return flag;
}
sumTemp -= array[depth] * i;
}
return false;
}
上述问题的考虑角度主要为:
1、求连续和为最优解一般是 最大字段和,求连续和为指定值一般是 前缀和,不一定连续的情况考虑使用背包
2、针对(5)输出结果,获取集合类最方便的是回溯,求最值问题一般是DP
3、针对(2)选取方式和(6)输出结果,考虑加锁
4、补充:回溯算法,递归的for循环中,i从0开始,是排列问题,从上次的深度depth开始,是组合问题,排列去重考虑加锁