Skip to content

Commit

Permalink
动态规划 背包问题
Browse files Browse the repository at this point in the history
  • Loading branch information
nibnait committed Oct 29, 2020
1 parent 3551f23 commit 9d4a2a9
Show file tree
Hide file tree
Showing 17 changed files with 841 additions and 57 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
package algorithm_practice.LeetCode.code000;

import org.junit.Assert;
import org.junit.Test;

/*
给你两个单词 word1 和 word2,请你计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
 
示例 1:
输入:word1 = "horse", word2 = "ros"
输出:3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例 2:
输入:word1 = "intention", word2 = "execution"
输出:5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/edit-distance
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
public class H072_编辑距离 {

@Test
public void testCase() {
String word1 = "horse";
String word2 = "ros";
int minDistance = 3;
Assert.assertEquals(minDistance, minDistance(word1, word2));

word1 = "intention";
word2 = "execution";
minDistance = 5;
Assert.assertEquals(minDistance, minDistance(word1, word2));

}

/**
* 空间优化
*/
public int minDistance(String word1, String word2) {
int l1 = word1.length();
int l2 = word2.length();

if (l1 == 0) {
return l2;
}

if (l2 == 0) {
return l1;
}

int dp_i_1 = 1;
int dp_j_1 = 1;
int dp_i_1_j_1 = 0;
int dp_i_j = 0;

for (int i = 1; i <= l1; i++) {
for (int j = 1; j <= l2; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp_i_j = dp_i_1_j_1;
// todo 相邻位置的变化

} else {
dp_i_j = Math.min(dp_i_1 + 1, Math.min(dp_j_1 + 1, dp_i_1_j_1 + 1));
// todo 相邻位置的变化

}
}
}

return dp_i_j;
}

/**
* 动态规划
*/
public int minDistance_normal(String word1, String word2) {
int l1 = word1.length();
int l2 = word2.length();

// dp[i][j]: w1[0...i-1] 与 w2[0...j-1] 的最短编辑距离
int[][] dp = new int[l1+1][l2+1];

for (int i = 0; i <= l1; i++) {
dp[i][0] = i;
}

for (int j = 0; j <= l2; j++) {
dp[0][j] = j;
}

for (int i = 1; i <= l1; i++) {
for (int j = 1; j <= l2; j++) {
if (word1.charAt(i-1) == word2.charAt(j-1)) {
dp[i][j] = dp[i-1][j-1];
} else {
dp[i][j] = min(
dp[i][j - 1] + 1, // word2的 j 往前移动1位:word1新增一个字符
dp[i - 1][j] + 1, // word1的 i 往前移动1位:word1直接删除一个字符
dp[i - 1][j - 1] + 1 // word1 和 word2 一起移动,代表直接替换字符
);
}
}
}

return dp[l1][l2];
}

private int min(int a, int b, int c) {
return Math.min(a, Math.min(b, c));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package algorithm_practice.LeetCode.code000;

import com.google.common.collect.Lists;
import org.junit.Assert;
import org.junit.Test;

import java.util.*;

/*
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
  [2,2,2,2],
  [2,3,3],
  [3,5]
]
 
提示:
1 <= candidates.length <= 30
1 <= candidates[i] <= 200
candidate 中的每个元素都是独一无二的。
1 <= target <= 500
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
public class M039_组合总和 {

@Test
public void testCase() {
int[] candidates = new int[]{2,3,6,7};
int target = 7;
List<List<Integer>> excepted = Lists.newArrayList(
Lists.newArrayList(2,2,3),
Lists.newArrayList(7));
Assert.assertEquals(excepted, combinationSum(candidates, target));

candidates = new int[]{2,3,5};
target = 8;
excepted = Lists.newArrayList(
Lists.newArrayList(2,2,2,2),
Lists.newArrayList(2,3,3),
Lists.newArrayList(3,5));
Assert.assertEquals(excepted, combinationSum(candidates, target));

}

public List<List<Integer>> combinationSum(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Deque<Integer> path = new LinkedList<>();

dfs(res, path, 0, candidates.length, candidates, target);
return res;
}

private void dfs(List<List<Integer>> res, Deque<Integer> path, int begin, int end, int[] candidates, int target) {
if (target < 0) {
return;
}

if (target == 0) {
res.add(new ArrayList<>(path));
}

for (int i = begin; i < end; i++) {
path.addLast(candidates[i]);

dfs(res, path, i, end, candidates, target - candidates[i]);

path.removeLast();
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
package algorithm_practice.LeetCode.code000;

import com.google.common.collect.Lists;
import org.junit.Assert;
import org.junit.Test;

import java.util.*;
import java.util.stream.Collectors;

/*
给定一个数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用一次。
说明:
所有数字(包括目标数)都是正整数。
解集不能包含重复的组合。 
示例 1:
输入: candidates = [10,1,2,7,6,1,5], target = 8,
所求解集为:
[
[1, 7],
[1, 2, 5],
[2, 6],
[1, 1, 6]
]
示例 2:
输入: candidates = [2,5,2,1,2], target = 5,
所求解集为:
[
  [1,2,2],
  [5]
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/combination-sum-ii
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
*/
public class M040_组合总和2 {

@Test
public void testCase() {
int[] candidates = new int[]{10,1,2,7,6,1,5};
int target = 8;
List<List<Integer>> excepted = Lists.newArrayList(
Lists.newArrayList(1, 2, 5),
Lists.newArrayList(1,7),
Lists.newArrayList(2, 6),
Lists.newArrayList(1, 1, 6));
Assert.assertEquals(excepted, combinationSum2(candidates, target));

candidates = new int[]{2,5,2,1,2};
target = 5;
excepted = Lists.newArrayList(
Lists.newArrayList(1,2,2),
Lists.newArrayList(5));
Assert.assertEquals(excepted, combinationSum2(candidates, target));

}

public List<List<Integer>> combinationSum2(int[] candidates, int target) {
List<List<Integer>> res = new ArrayList<>();
Deque<Integer> path = new LinkedList<>();

// 先排序,为了方便后面好判断是否真正只用了1次 candidates 里面的数字
Arrays.sort(candidates);

dfs(res, path, 0, candidates.length, candidates, target);
return res;
}

private void dfs(List<List<Integer>> res, Deque<Integer> path, int begin, int end, int[] candidates, int target) {
if (target < 0) {
return;
}

if (target == 0) {
res.add(new ArrayList<>(path));
}

for (int i = begin; i < end; i++) {

// candidates[i] 不是begin位置的第一个数字
// 且 上一个candidates[i-1] 如果与当前的 candidates[i]相等,说明与同一层的选择重复,需剪枝。
if (i>begin && candidates[i-1] == candidates[i]) {
continue;
}

path.addLast(candidates[i]);
dfs(res, path, i+1, end, candidates, target-candidates[i]);
path.removeLast();
}

}

/**
* 耗时有点高。。
* 每次都有重新遍历 res,重新排序。判断是否contains
*/
private void dfs_V1(List<List<Integer>> res, Deque<Integer> path, int begin, int end, int[] candidates, int target) {
if (target < 0) {
return;
}

if (target == 0 && !contains(res, path)) {
res.add(new ArrayList<>(path));
}

for (int i = begin; i < end; i++) {

path.addLast(candidates[i]);

dfs_V1(res, path, i+1, end, candidates, target - candidates[i]);

path.removeLast();
}
}

private boolean contains(List<List<Integer>> res, Deque<Integer> path) {
List<List<Integer>> sortedRes = new ArrayList<>();
for (List<Integer> oldPath : res) {
sortedRes.add(oldPath.stream().sorted().collect(Collectors.toList()));
}

// res = sortedRes;
return sortedRes.contains(path.stream().sorted().collect(Collectors.toList()));
}

}
Loading

0 comments on commit 9d4a2a9

Please sign in to comment.