目录
题目1:给定一个有序数组arr,代表坐落在X轴上的点,给定一个正数K,代表绳子的长度。返回绳子最多压中几个点?即使绳子边缘处盖住点也算盖住。
题目3:(leetcode494)给定一个数组arr,你可以在每个数字之前决定+或者-,但是必须所有数字都参与。再给定一个数target,请问最后算出target的方法数是多少。
题目6:(leetcode3)求一个字符串中,最长无重复字符子串长度
题目7:给定一个数组arr,代表每个人的能力值。在给定一个非负数k,如果俩个人能力差值正好为k,那么可以凑在一起比赛。一局比赛只有俩个人,返回最多可以同时有多少场比赛
题目9:(leetcode53)返回一个数组中,子数组最大累加和
题目12:如果一个节点X,它左树结构和右树结构完全一样。那么我们说以X为头的树是相等树,给定一颗二叉树的头节点head。返回head整棵树上有多少棵相等子树。
题目16:(leetcode301)给你一个由若干括号和字母组成的字符串s,删除最小数量的无效括号,使得输入的字符串有效,返回所有可能的结果。
题目18:定义何为step num?比如680,680+68+6=754,680的step num叫754。给定一个正数num,判断它是不是某个数的step num
题目20:面试题 08.14. 布尔运算 - 力扣(LeetCode)
题目22:约瑟夫环问题-剑指 Offer 62. 圆圈中最后剩下的数字 - 力扣(LeetCode)
题目1:给定一个有序数组arr,代表坐落在X轴上的点,给定一个正数K,代表绳子的长度。返回绳子最多压中几个点?即使绳子边缘处盖住点也算盖住。
解题思路:用俩个指针L和R,判断它们的距离差,看情况R++或者L++。
时间复杂度是O(N)
public static int maxPoint2(int[] arr, int L) {
int left = 0;
int right = 0;
int N = arr.length;
int max = 0;
while (left < N) {
while (right < N && arr[right] - arr[left] <= L) {
right++;
}
max = Math.max(max, right - (left++));
}
return max;
}
题目2:一个数组中只有俩个字符'G'和'B',可以让所有的G都放在左侧,所有的B都放在右侧。或者可以让所有的G都放在右侧,所有的B都放在左侧。但是只能在相邻字符之间进行交换操作,问返回至少需要交换几次。
解题思路:通过指针的方式去求解。
public static int minSteps2(String s) {
if (s == null || s.equals("")) {
return 0;
}
char[] str = s.toCharArray();
int step1 = 0;
int step2 = 0;
int gi = 0;
int bi = 0;
for (int i = 0; i < str.length; i++) {
if (str[i] == 'G') { // 当前的G,去左边 方案1
step1 += i - (gi++);
} else {// 当前的B,去左边 方案2
step2 += i - (bi++);
}
}
return Math.min(step1, step2);
}
题目3:(leetcode494)给定一个数组arr,你可以在每个数字之前决定+或者-,但是必须所有数字都参与。再给定一个数target,请问最后算出target的方法数是多少。
第一种方式暴力递归
public static int findTargetSumWays1(int[] arr, int s) {
return process1(arr, 0, s);
}
public static int process1(int[] arr, int index, int rest) {
if (index == arr.length) {
return rest == 0 ? 1 : 0;
}
return process1(arr, index + 1, rest - arr[index]) + process1(arr, index + 1, rest + arr[index]);
}
第二种方式记忆化搜索(动态规划)
public static int findTargetSumWays2(int[] arr, int s) {
return process2(arr, 0, s, new HashMap<>());
}
//HashMap<Integer, HashMap<Integer, Integer>> dp
// index + rest
// index == 7 rest = 13 256
// index == 7 rest = 35 17
//{
// 7 : { 13 , 256}
// : { 35 , 17}
//}
public static int process2(int[] arr, int index, int rest, HashMap<Integer, HashMap<Integer, Integer>> dp) {
if (dp.containsKey(index) && dp.get(index).containsKey(rest)) {
return dp.get(index).get(rest);
}
// 否则,没命中!
int ans = 0;
if (index == arr.length) {
ans = rest == 0 ? 1 : 0;
} else {
ans = process2(arr, index + 1, rest - arr[index], dp) + process2(arr, index + 1, rest + arr[index], dp);
}
if (!dp.containsKey(index)) {
dp.put(index, new HashMap<>());
}
dp.get(index).put(rest, ans);
return ans;
}
第三种方式动态规划(二维动态规划的空间压缩技巧)
理解业务过程
// 优化点一 :
// 你可以认为arr中都是非负数
// 因为即便是arr中有负数,比如[3,-4,2]
// 因为你能在每个数前面用+或者-号
// 所以[3,-4,2]其实和[3,4,2]达成一样的效果
// 那么我们就全把arr变成非负数,不会影响结果的
// 优化点二 :
// 如果arr都是非负数,并且所有数的累加和是sum
// 那么如果target<sum,很明显没有任何方法可以达到target,可以直接返回0
// 优化点三 :
// arr内部的数组,不管怎么+和-,最终的结果都一定不会改变奇偶性
// 所以,如果所有数的累加和是sum,
// 并且与target的奇偶性不一样,没有任何方法可以达到target,可以直接返回0
// 优化点四 :
// 比如说给定一个数组, arr = [1, 2, 3, 4, 5] 并且 target = 3
// 其中一个方案是 : +1 -2 +3 -4 +5 = 3
// 该方案中取了正的集合为P = {1,3,5}
// 该方案中取了负的集合为N = {2,4}
// 所以任何一种方案,都一定有 sum(P) - sum(N) = target
// 现在我们来处理一下这个等式,把左右两边都加上sum(P) + sum(N),那么就会变成如下:
// sum(P) - sum(N) + sum(P) + sum(N) = target + sum(P) + sum(N)
// 2 * sum(P) = target + 数组所有数的累加和
// sum(P) = (target + 数组所有数的累加和) / 2
// 也就是说,任何一个集合,只要累加和是(target + 数组所有数的累加和) / 2
// 那么就一定对应一种target的方式
// 也就是说,比如非负数组arr,target = 7, 而所有数累加和是11
// 求有多少方法组成7,其实就是求有多少种达到累加和(7+11)/2=9的方法
// 优化点五 :
// 二维动态规划的空间压缩技巧
public static int findTargetSumWays(int[] arr, int target) {
int sum = 0;
for (int n : arr) {
sum += n;
}
return sum < target || ((target & 1) ^ (sum & 1)) != 0 ? 0 : subset2(arr, (target + sum) >> 1);
}
// 求非负数组nums有多少个子集,累加和是s
// 二维动态规划
// 不用空间压缩
public static int subset1(int[] nums, int s) {
if (s < 0) {
return 0;
}
int n = nums.length;
// dp[i][j] : nums前缀长度为i的所有子集,有多少累加和是j?
int[][] dp = new int[n + 1][s + 1];
// nums前缀长度为0的所有子集,有多少累加和是0?一个:空集
dp[0][0] = 1;
for (int i = 1; i <= n; i++) {
for (int j = 0; j <= s; j++) {
dp[i][j] = dp[i - 1][j];
if (j - nums[i - 1] >= 0) {
dp[i][j] += dp[i - 1][j - nums[i - 1]];
}
}
}
return dp[n][s];
}
// 不会空间压缩
// 求非负数组nums有多少个子集,累加和是s
// 二维动态规划
// 用空间压缩:
// 核心就是for循环里面的:for (int i = s; i >= n; i--) {
// 为啥不枚举所有可能的累加和?只枚举 n...s 这些累加和?
// 因为如果 i - n < 0,dp[i]怎么更新?和上一步的dp[i]一样!所以不用更新
// 如果 i - n >= 0,dp[i]怎么更新?上一步的dp[i] + 上一步dp[i - n]的值,这才需要更新
public static int subset2(int[] nums, int s) {
if (s < 0) {
return 0;
}
int[] dp = new int[s + 1];
dp[0] = 1;
for (int n : nums) {
for (int i = s; i >= n; i--) {
dp[i] += dp[i - n];
}
}
return dp[s];
}
题目4:现有司机M人,调度中心会将所有司机平分给A、B俩个区域。第i个司机去A可得到收入为income[i][0],第i个司机去B可得到收入为income[i][2],那么返回所有调度方案中能使所有司机总收入最高的方案,是多少钱。
public static int maxMoney1(int[][] income) {
if (income == null || income.length < 2 || (income.length & 1) != 0) {
return 0; //(income.length & 1) != 0) 位运算,看其奇偶性
}
int N = income.length; // 司机数量一定是偶数,所以才能平分,A N /2 B N/2
int M = N >> 1; // M = N / 2 要去A区域的人
return process1(income, 0, M);
}
// index.....所有的司机,往A和B区域分配!
// A区域还有rest个名额!
// 返回把index...司机,分配完,并且最终A和B区域同样多的情况下,
// index...这些司机,整体收入最大是多少!
// 因为A区域能够推断出B区域。所以只需要一个区域变量就可以
public static int process1(int[][] income, int index, int rest) {
if (index == income.length) {
return 0;
}
// 还剩下司机!
if (income.length - index == rest) {
return income[index][0] + process1(income, index + 1, rest - 1);
}
if (rest == 0) {
return income[index][1] + process1(income, index + 1, rest);
}
// 当前司机,可以去A,或者去B
int p1 = income[index][0] + process1(income, index + 1, rest - 1);
int p2 = income[index][1] + process1(income, index + 1, rest);
return Math.max(p1, p2);
}
题目5:HashMap实现setAll的功能。
import java.util.HashMap;
public class Code05_SetAll {
public static class MyValue<V> {
public V value;
public long time;
public MyValue(V v, long t) {
value = v;
time = t;
}
}
public static class MyHashMap<K, V> {
private HashMap<K, MyValue<V>> map;
private long time;
private MyValue<V> setAll;
public MyHashMap() {
map = new HashMap<>();
time = 0;
setAll = new MyValue<V>(null, -1);
}
public void put(K key, V value) {
map.put(key, new MyValue<V>(value, time++));
}
public void setAll(V value) {
setAll = new MyValue<V>(value, time++);
}
public V get(K key) {
if (!map.containsKey(key)) {
return null;
}
if (map.get(key).time > setAll.time) {
return map.get(key).value;
} else {
return setAll.value;
}
}
}
}
题目6:(leetcode3)求一个字符串中,最长无重复字符子串长度
暴力方法:俩个for循环即可实现。
我们是不是可以:每一个数字,都往左边推,去寻找目标S。取其MAX即可。
当子串、当子数组。。。。遇到这种问题的时候,就这么想~
从 0==》S 从 1 ==》 S
那么从整体上看,是不是从左到右的计算过程。那么我i位置上的内容,是不是可以依赖于i-1得出。
所以,此时根据此思想,可以想到加速的办法。
1)当前字符上次的位置
2)i-1位置往左推的距离
取MAX即可。又因为,我们只需要得到上一次的内容,所有只需要变量记录即可!!!
public class Code06_LongestSubstringWithoutRepeatingCharacters {
public static int lengthOfLongestSubstring(String s) {
if (s == null || s.equals("")) {
return 0;
}
char[] str = s.toCharArray();
// ASCII 只有 0~255
// map[i] = v i这个ascii码的字符,上次出现在V位置
int[] map = new int[256];
for (int i = 0; i < 256; i++) {
map[i] = -1;
}
map[str[0]] = 0;
int N = str.length;
int ans = 1; // 最少长度 1
int pre = 1; // 上一个位置,向左推了多长
for (int i = 1; i < N; i++) {
pre = Math.min(i - map[str[i]], pre + 1);
ans = Math.max(ans, pre);
map[str[i]] = i;
}
return ans;
}
}
题目7:给定一个数组arr,代表每个人的能力值。在给定一个非负数k,如果俩个人能力差值正好为k,那么可以凑在一起比赛。一局比赛只有俩个人,返回最多可以同时有多少场比赛
题意解析: [ 3 1 5 7 ] k = 2 此时最多安排2场比赛 1 3 和 5 7
此时根据题意:可以想到一个暴力求解的方法,