Skip to content

Commit cc6c976

Browse files
committed
剑指offer新版
1 parent 3ef0381 commit cc6c976

File tree

11 files changed

+537
-24
lines changed

11 files changed

+537
-24
lines changed

leetcode_Java/Solution0121.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,4 +79,19 @@ public int maxProfit(int[] prices) {
7979
}
8080
return maxProfit;
8181
}
82+
}
83+
84+
public class Solution {
85+
public int maxProfit (int[] prices) {
86+
int n = prices.length;
87+
if (n < 2) {
88+
return 0;
89+
}
90+
int minPrice = prices[0], profit = 0;
91+
for (int i = 1; i < n; i++) {
92+
profit = Math.max(profit, prices[i] - minPrice);
93+
minPrice = Math.min(minPrice, prices[i]);
94+
}
95+
return profit;
96+
}
8297
}

剑指Offer_新版_java/DoneType.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@
6363

6464
五、动态规划
6565
10. 斐波那契数列
66+
14. 剪绳子
6667
19. 正则表达式匹配
6768
42. 连续子数组的最大和
6869
46. 把数字翻译成字符串

剑指Offer_新版_java/JZ14.java

Lines changed: 69 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,81 @@
11
// 14. 剪绳子
22

33

4+
/*
5+
动态规划:
6+
1、题目简化:求长度为n的绳子能得到的最大乘积
7+
2、定义dp数组:dp[i] 表示长度为i的绳子能得到的最大乘积
8+
3、初始化:dp[0]=1, dp[1]=1
9+
4、状态转移方程
10+
先把长度为i的绳子拆成两部分,一部分是j,另一部分是i-j,那么会有下面4种情况
11+
1)j和i-j都不能再拆了 dp[i] = j * (i-j);
12+
2)j能拆,i-j不能拆 dp[i] = dp[j] * (i-j);
13+
3)j不能拆,i-j能拆 dp[i] = j * dp[i-j];
14+
4)j和i-j都能拆 dp[i] = dp[j] * dp[i-j];
15+
取上面4种情况的最大值即可,即两部分在能拆和不能拆时取最大值,然后相乘。由于有多种分段方式,所以多结果再进行比较取最大,整理得到递推公式
16+
dp[i] = max(dp[i], max(j, dp[j]) * max(i - j, dp[i - j]));
17+
5、遍历dp数组填表
18+
1)长度为i的绳子能得到的最大乘积,即dp[i],需要先计算dp[1]到dp[i-1]的值,自底向上计算
19+
2)第一个for循环遍历绳子长度,从小到大,从2开始到目标值
20+
3)第二个for循环遍历绳子分段后左半部分的长度,遍历到绳子长度一半即可,因为再往后遍历的情况前面已经计算了
21+
6、返回结果
22+
*/
423
public class Solution {
5-
public int cutRope(int target) {
6-
if (target == 2) {
7-
return 1;
24+
public int cutRope (int target) {
25+
int[] dp = new int[target + 1];
26+
dp[1] = 1;
27+
for (int i = 2; i <= target; i++) {
28+
for (int j = 1; j <= i / 2; j++) {
29+
dp[i] = Math.max(dp[i], Math.max(j, dp[j]) * Math.max(i - j, dp[i - j]));
30+
}
31+
}
32+
return dp[target];
33+
}
34+
}
35+
36+
37+
/*
38+
数学:
39+
1、一个整数先把他分成两部分,x+y=n(假设x>=y并且x-y<=1,也就是说x和y非常接近)那么乘积是x*y。
40+
然后我们再把这两部分的差放大(x+1)+(y-1)=n(假设x>=y);他们的乘积是(x+1)*(y-1)=x*y-(x-y)-1,很明显是小于x*y的。
41+
所以我们得出结论,如果把整数n分为两部分,那么这两部分的值相差越小乘积越大。
42+
同理还可以证明如果分成3部分,4部分……也是相差越小乘积会越大。
43+
2、如果我们把长度为n的绳子分为x段,则每段只有在长度相等的时候乘积最大,那么每段的长度是n/x。所以他们的乘积是(n/x)^x。
44+
我们来对这个函数求导 y=(n/x)^x
45+
通过对函数求导我们发现,当x=n/e的时候,也就是每段绳子的长度是n/x=n/(n/e)=e的时候乘积最大。
46+
我们知道e=2.718281828459。而题中我们的绳子剪的长度都是整数,所以不可能取e,我们只能取接近e的值,也就是3的时候乘积最大。
47+
3、但也有例外,当n<=4的时候会有特殊情况,因为2*2>1*3
48+
4、如果n大于4,我们不停的把绳子减去3,并且结果不断乘3,最终结果再乘剩余的target
49+
*/
50+
public class Solution {
51+
public int cutRope (int target) {
52+
if (target == 2 || target == 3) {
53+
return target - 1;
854
}
9-
if (target == 3) {
10-
return 2;
55+
int res = 1;
56+
while (target > 4) {
57+
target -= 3;
58+
res *= 3;
59+
}
60+
return res * target;
61+
}
62+
}
63+
64+
65+
/*
66+
1、以上解法通过公式直接计算
67+
2、x表示余数,余1时跟一个3凑成4进行乘积更大,余2时最后乘上即可,余0则刚好等分直接计算
68+
3、y表示有多少段3,
69+
*/
70+
public class Solution {
71+
public int cutRope(int target) {
72+
if (target == 2 || target == 3) {
73+
return target - 1;
1174
}
1275
int x = target % 3;
1376
int y = target / 3;
1477
if (x == 1) {
15-
return (int) Math.pow(3, y - 1) * 2 * 2;
78+
return (int) Math.pow(3, y - 1) * 4;
1679
} else if (x == 2) {
1780
return (int) Math.pow(3, y) * 2;
1881
} else {

剑指Offer_新版_java/JZ42.java

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,34 @@
11
// 42. 连续子数组的最大和
22

33

4+
/*
5+
动态规划:
6+
1、题目简化:求连续子数组最大和
7+
定义dp数组:dp[i] 表示以索引i结尾的连续子数组的最大和
8+
2、初始化:dp[0] = array[0] 表示首元素结尾的最大和是对应元素值
9+
3、状态转移方程
10+
1)每次遇到一个新元素,如果前面的连续子数组如果为正数,能提供收益,那么加上后当前元素结尾的连续子数组会变得更大,
11+
如果前面的连续子数组如果为负数,不能提供收益,那么直接丢弃,单独它本身会更大
12+
2)dp[i] = Math.max(dp[i - 1] + array[i], array[i])
13+
4、遍历dp数组填表:从索引1开始遍历,根据状态转移方程填表
14+
5、返回结果:最大的状态就是结果
15+
*/
16+
public class Solution {
17+
public int FindGreatestSumOfSubArray(int[] array) {
18+
int n = array.length;
19+
int[] dp = new int[n];
20+
dp[0] = array[0];
21+
for (int i = 1; i < n; i++) {
22+
dp[i] = Math.max(dp[i - 1] + array[i], array[i]);
23+
}
24+
return Arrays.stream(dp).max().getAsInt();
25+
}
26+
}
27+
28+
29+
/*
30+
贪心
31+
*/
432
public class Solution {
533
public int FindGreatestSumOfSubArray(int[] array) {
634
int maxNum = Arrays.stream(array).max().getAsInt();

剑指Offer_新版_java/JZ46.java

Lines changed: 37 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,44 @@
1-
// 46. 把数组排成最小的数
1+
// 46. 把数字翻译成字符串
22

33

4+
/*
5+
动态规划:
6+
1、先排除几种特殊情况
7+
1)排除0,无法译码
8+
2)排除只有一种可能的10和20
9+
3)排除当0的前面不是1或2时,无法译码
10+
2、定义dp数组:dp[i] 表示前i个字符的译码方式数
11+
3、初始化:填表为1,表示每个字符的译码方式数为1
12+
4、状态转移方程
13+
1)对于一个字符,如果直接译码,则dp[i] = dp[i - 1];如果组合译码,则dp[i] = dp[i - 2]
14+
2)当前字符与前一字符构成 11-19 21-26时,有两种译码方式,则dp[i] = dp[i - 1] + dp[i - 2]
15+
否则只有一种译码方式,则dp[i] = dp[i - 1]
16+
5、遍历dp数组填表:一个for循环遍历dp数组,根据状态转移方程填表
17+
6、返回结果:最后一个状态就是结果
18+
*/
419
public class Solution {
5-
public String PrintMinNumber(int[] numbers) {
6-
List<Integer> list = new ArrayList<Integer>();
7-
for (int i : numbers) {
8-
list.add(i);
20+
public int solve (String nums) {
21+
if ("0".equals(nums)) {
22+
return 0;
923
}
10-
Collections.sort(list, new Comparator<Integer>() {
11-
// 返回负数则第一个参数放在前面,正数或零则第一个参数放在后面
12-
@Override
13-
public int compare(Integer o1, Integer o2) {
14-
String str1 = o1 + "" + o2;
15-
String str2 = o2 + "" + o1;
16-
return str1.compareTo(str2);
24+
if (nums == "10" || nums == "20") {
25+
return 1;
26+
}
27+
int n = nums.length();
28+
for (int i = 1; i < n; i++) {
29+
if (nums.charAt(i) == '0' && nums.charAt(i - 1) != '1' && nums.charAt(i - 1) != '2') {
30+
return 0;
31+
}
32+
}
33+
int[] dp = new int[n + 1];
34+
Arrays.fill(dp, 1);
35+
for (int i = 2; i <= n; i++) {
36+
if ((nums.charAt(i - 2) == '1' && nums.charAt(i - 1) != '0') || (nums.charAt(i - 2) == '2' && nums.charAt(i - 1) >= '1' && nums.charAt(i - 1) <= '6')) {
37+
dp[i] = dp[i - 1] + dp[i - 2];
38+
} else {
39+
dp[i] = dp[i - 1];
1740
}
18-
});
19-
StringBuilder sb = new StringBuilder();
20-
for (Integer i : list) {
21-
sb.append(i);
2241
}
23-
return sb.toString();
42+
return dp[n];
2443
}
25-
}
44+
}

剑指Offer_新版_java/JZ47.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// 47. 礼物的最大价值
2+
3+
4+
// 64. 最小路径和(力扣,思路相同,本题是 最大路径和)
5+
/*
6+
1、题目简化:求从 (0,0) 到达 (m-1,n-1) 位置的最大路径和
7+
2、定义dp数组:本题可以直接在原数组操作。grid[i][j] 表示到达 (i,j) 位置的最大路径和
8+
3、初始化:
9+
1)二维dp数组不用扩容,直接根据dp数组的定义就可以直观地对应进行初始化
10+
2)首行首列的路径和进行累加初始化
11+
4、状态转移方程:grid[i][j] += Math.max(grid[i - 1][j], grid[i][j - 1]); 即上方或左方的最大路径和
12+
5、遍历顺序:两个for循环遍历二维dp数组每个位置,根据状态转移方程计算该位置的最大路径和并填表,直到终点,获得结果
13+
*/
14+
class Solution {
15+
public int minPathSum(int[][] grid) {
16+
int n = grid.length, m = grid[0].length;
17+
for (int i = 1; i < n; i++) {
18+
grid[i][0] += grid[i - 1][0];
19+
}
20+
for (int j = 1; j < m; j++) {
21+
grid[0][j] += grid[0][j - 1];
22+
}
23+
for (int i = 1; i < n; i++) {
24+
for (int j = 1; j < m; j++) {
25+
grid[i][j] += Math.max(grid[i - 1][j], grid[i][j - 1]);
26+
}
27+
}
28+
return grid[n - 1][m - 1];
29+
}
30+
}
31+
32+
33+
public class Solution {
34+
public int maxValue (int[][] grid) {
35+
int m = grid.length, n = grid[0].length;
36+
int[][] dp = new int[m][n];
37+
dp[0][0] = grid[0][0];
38+
for (int i = 1; i < m; i++) {
39+
dp[i][0] = dp[i - 1][0] + grid[i][0];
40+
}
41+
for (int j = 1; j < n; j++) {
42+
dp[0][j] = dp[0][j - 1] + grid[0][j];
43+
}
44+
for (int i = 1; i < m; i++) {
45+
for (int j = 1; j < n; j++) {
46+
dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + grid[i][j];
47+
}
48+
}
49+
return dp[m - 1][n - 1];
50+
}
51+
}

剑指Offer_新版_java/JZ48.java

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
// 48. 最长不含重复字符的子字符串
2+
//同“3. 无重复字符的最长子串”(力扣)
3+
4+
5+
/*
6+
索引定位收缩左区间:
7+
1、HashMap保存滑动窗口中的字符的索引
8+
2、如果右指针移动过程新的字符 出现 在滑动窗口中,则更新左指针位置
9+
3、更新当前字符在HashMap中的索引
10+
4、右指针每移动一次后,计算新窗口的长度,比较并保存最大长度
11+
*/
12+
public class Solution {
13+
public int lengthOfLongestSubstring (String s) {
14+
int maxLen = 0, start = 0;
15+
Map<Character, Integer> map = new HashMap<>();
16+
for (int i = 0; i < s.length(); i++) {
17+
char c = s.charAt(i);
18+
if (map.containsKey(c)) {
19+
start = Math.max(start, map.get(c) + 1);
20+
}
21+
map.put(c, i);
22+
maxLen = Math.max(maxLen, i - start + 1);
23+
}
24+
return maxLen;
25+
}
26+
}
27+
28+
29+
/*
30+
左指针右移收缩左区间:
31+
1、HashMap保存滑动窗口中的字符的出现次数
32+
2、如果右指针移动过程新的字符 没有 在滑动窗口中,则将字符添加到HashMap中,次数记为1
33+
3、如果右指针移动过程新的字符 出现 在滑动窗口中,则移动左指针直到同样该字符的下一位,移动过程指向的字符在HashMap中出现次数减1
34+
4、右指针每移动一次后,计算新窗口的长度,比较并保存最大长度
35+
*/
36+
class Solution {
37+
public int lengthOfLongestSubstring(String s) {
38+
int maxLen = 0, start = 0;
39+
Map<Character, Integer> map = new HashMap<>();
40+
for (int i = 0; i < s.length(); i++) {
41+
char c = s.charAt(i);
42+
if (map.getOrDefault(c, 0) == 0) {
43+
map.put(c, 1);
44+
} else {
45+
while (s.charAt(start) != c) {
46+
map.put(s.charAt(start), map.get(s.charAt(start)) -1);
47+
start++;
48+
}
49+
start++;
50+
}
51+
maxLen = Math.max(maxLen, i - start + 1);
52+
}
53+
return maxLen;
54+
}
55+
}

剑指Offer_新版_java/JZ56.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
// 56. 数组中只出现一次的两个数字
2+
3+
4+
/*
5+
集合:用HashSet存放元素,存在则移除,不存在则添加,最终只剩下出现一次的数字,遍历集合元素加到数组,最后数组排序
6+
*/
7+
public class Solution {
8+
public int[] FindNumsAppearOnce (int[] array) {
9+
int[] res = new int[2];
10+
Set<Integer> set = new HashSet();
11+
for (int i = 0; i < array.length; i++) {
12+
if (set.contains(array[i])) {
13+
set.remove(array[i]);
14+
} else {
15+
set.add(array[i]);
16+
}
17+
}
18+
int i = 0;
19+
for (Integer num : set) {
20+
res[i++] = num;
21+
}
22+
Arrays.sort(res);
23+
return res;
24+
}
25+
}
26+
27+
28+
/*
29+
异或运算:
30+
1、异或公式
31+
1)一个数和 0 做 XOR 运算等于本身:a⊕0 = a
32+
2)一个数和其本身做 XOR 运算等于 0:a⊕a = 0
33+
3)XOR 运算满足交换律和结合律:a⊕b⊕a = (a⊕a)⊕b = 0⊕b = b
34+
2、先将整个数组元素进行异或运算,结果是 只出现一次的两个数字的异或运算结果
35+
3、对两个数字的异或结果进行1的与运算,从最低位开始找,找到二进制位首个两者不同的位置
36+
4、对所有数进行分组并异或运算,最终得到两个数字
37+
5、存入结果数组,并排序
38+
*/
39+
public class Solution {
40+
public int[] FindNumsAppearOnce (int[] array) {
41+
int temp = 0;
42+
for (int num : array) {
43+
temp ^= num;
44+
}
45+
int mask = 1;
46+
while ((temp & mask) == 0) {
47+
mask <<= 1;
48+
}
49+
int x = 0, y = 0;
50+
for (int num : array) {
51+
if ((num & mask) == 0) {
52+
x ^= num;
53+
} else {
54+
y ^= num;
55+
}
56+
}
57+
int[] res = new int[2];
58+
res[0] = x;
59+
res[1] = y;
60+
Arrays.sort(res);
61+
return res;
62+
}
63+
}

0 commit comments

Comments
 (0)