高级班讲解
题目一
/**
* 思路:假设数组的长度为n,准备n+1个桶,将数组中的数平均的放入这n+1个桶中,每个桶只保留桶内的最大值和最小值。相邻数的最大值一定在相邻桶的最大值和最小值之间产生。
*
* @param nums 给定的数组
* @return 返回相邻两数的最大差值
*/
public static int maxGap(int[] nums) {
if (nums == null || nums.length < 2) {
return 0;
}
int length = nums.length; //数组的长度
boolean[] hasNum = new boolean[length + 1]; //用于标识桶中是否有数
int[] max = new int[length + 1]; //用于标识桶中的最大值
int[] min = new int[length + 1]; //用于标识桶中的最小值
int maxNum = Integer.MIN_VALUE; //找到数组中的最大值
int minNum = Integer.MAX_VALUE; //找到数组中的最小值
for (int num : nums) {
maxNum = Math.max(maxNum, num);
minNum = Math.min(minNum, num);
}
if (minNum == maxNum) { //如果最大值和最小值相同,则数组中的数都一样,差值为0
return 0;
}
int bid = 0; //标识桶号
for (int i = 0; i < nums.length; i++) {
bid=findBucket(nums[i],length,maxNum,minNum);
max[bid]=hasNum[bid]?Math.max(max[bid],nums[i]):nums[i]; //如果桶中有数,则比较,如果桶中没数,则直接赋值
min[bid]=hasNum[bid]?Math.min(min[bid],nums[i]):nums[i];
hasNum[bid]=true;
}
int maxGap=0;
int lastMax=max[0];
for(int i=1;i<length+1;i++){
if(hasNum[i]) {
maxGap = Math.max(min[i] - lastMax, maxGap);
lastMax=max[i];
}
}
return maxGap;
}
//找到数对应的桶号
public static int findBucket(long nums, long len, long max, long min) {
return (int) ((nums - min) * len / (max - min));
}
题目二
public static int mostEOR(int[] arr) {
int ans = 0;
int xor = 0; //异或和
int[] mosts = new int[arr.length];
HashMap<Integer, Integer> map = new HashMap<>(); //key:从0出发的某个前缀异或和 value:这个前缀异或和出现的最晚位置
map.put(0, -1);
for (int i = 0; i < arr.length; i++) { //i所在的最后一块
xor ^= arr[i]; //0...i范围内的异或和
if (map.containsKey(xor)) { //如果map中存在过这个异或和
int pre = map.get(xor); //获取到这个异或和上次出现的位置
mosts[i] = pre == -1 ? 1 : (mosts[pre] + 1); //如果pre=-1,说明前面没出现过,从0...i只有一种方法,如果pre!=-1,说明前面已经出现过这个异或和,则从pre+1.....i位置的异或和为0,所以以i位置结尾的最优解是mosts[pre]+1
}
if (i > 0) {
mosts[i]=Math.max(mosts[i-1],mosts[i]);
}
map.put(xor,i); //更新出现的最晚位置
ans=Math.max(ans,mosts[i]);
}
return ans;
}
题目三
/**
* 返回有多少种方法能够拼出money的面值
* 思路:先得出只用普通币得到m有多少种方法,再得出只用纪念币得到m有多少种方法,最后计算用普通币得到a,纪念币得到m-b的方法数
*
* @param arbitrary 数组中的面值每种可以取任意枚
* @param onlyone 数组中的面值每种最多只能取一枚
* @param money 需要拼出的面值
* @return 方法数
*/
public static int moneyWays(int[] arbitrary, int[] onlyone, int money) {
if (money < 0) {
return 0;
}
if ((arbitrary == null || arbitrary.length == 0) && (onlyone == null || onlyone.length == 0)) {
return money == 0 ? 1 : 0;
}
int[][] arbiGetM = new int[arbitrary.length][money + 1]; //得到用普通币获得money的方法数
int[][] onlyGetM = new int[onlyone.length][money + 1]; //得到用纪念币获得money的方法数
for (int i = 0; i <= money; i++) {
if (i % arbitrary[0] == 0) {
arbiGetM[0][i] = 1;
}
}
for (int i = 0; i < arbiGetM.length; i++) {
arbiGetM[i][0] = 1;
}
for (int i = 1; i < arbiGetM.length; i++) {
for (int j = 1; j <= money; j++) {
if (j - arbitrary[i] >= 0) {
arbiGetM[i][j] = arbiGetM[i - 1][j] + arbiGetM[i][j - arbitrary[i]]; //此处使用了斜率优化,本来arbiGetM[i][j]=arbiGetM[i-1][j]+arbiGetM[i-1][j-arbitrary[i]]+arbiGetM[i-1][j-2*arbitrary[i]](只需要满足j-k*arbitrary[i]≥0即可)
} else {
arbiGetM[i][j] = arbiGetM[i - 1][j];
}
}
}
for (int i = 0; i < onlyGetM.length; i++) {
onlyGetM[i][0] = 1;
}
onlyGetM[0][onlyone[0]] = 1;
for (int i = 1; i < onlyGetM.length; i++) {
for (int j = 1; j <= money; j++) {
if (j - onlyone[i] >= 0) {
onlyGetM[i][j] = onlyGetM[i - 1][j] + onlyGetM[i - 1][j - onlyone[i]];
} else {
onlyGetM[i][j] = onlyGetM[i - 1][j];
}
}
}
int ans = 0;
for (int i = 0; i <= money; i++) {
ans += arbiGetM[arbiGetM.length - 1][i] * onlyGetM[onlyGetM.length - 1][money - i];
}
return ans;
}
题目四
public class FindKthMinNumber {
public static int findKthNum(int[] arr1, int[] arr2, int kth) {
if (arr1 == null || arr2 == null) {
throw new RuntimeException("Your arr is invalid!");
}
if (kth < 1 || kth > arr1.length + arr2.length) {
throw new RuntimeException("K is invalid!");
}
int[] shorts = arr1.length <= arr2.length ? arr1 : arr2;
int[] longs = arr2.length > arr1.length ? arr2 : arr1;
int l = longs.length;
int s = shorts.length;
//接下来把kth分成三类
//第一类,kth<=s,只要在两个数组的[0...kth-1]范围上求出上中位数即可
//第二类,s<kth<=l,在longs数组上[kth...l]位置上的数组是不可能的。
//接下来判断shorts[s-1]和longs[kth-s-1]哪个大,如果longs[kth-s-1]大于等于直接返回longs[kth-s-1],如果longs[kth-s-1]小,则手动排除掉
//判断longs[kth-1]和shorts[0]哪个大,如果longs[kth-1]小于等于直接返回longs[kth-1],如果longs[kth-1]大,则手动排除掉
//最后在shorts[0...s-2]和longs[kth-s...kth-2]上继续求中位数
//第三类,l<kth<=s+l,在shorts数组上[0...kth-l-2]位置上的数字是不可能的,longs数组上[0...kth-s-2]位置上的数是不可能的,
// 接下来判断shorts[kth-l-1]和longs[l-1]哪个大,如果shorts[kth-l-1]大于等于直接返回shorts[kth-l-1],如果shorts[kth-l-1]小,则手动排除掉
// 判断longs[kth-s-1]和shorts[s-1]哪个大,如果longs[kth-s-1]大于等于直接返回longs[kth-s-1],如果longs[kth-s-1]小,则手动排除掉
// 最后在shorts[kth-l.....s-1]和longs[kth-s...l-1]上继续求上中位数
if(kth<=s){
return getUpMedian(shorts,0,kth-1,longs,0,kth-1);
}
if(l<kth){
if(shorts[kth-l-1]>=longs[l-1]){
return shorts[kth-l-1];
}
if(longs[kth-s-1]>=shorts[s-1]){
return longs[kth-s-1];
}
return getUpMedian(shorts,kth-l,s-1,longs,kth-s,l-1);
}
if(shorts[s-1]<=longs[kth-s-1]){
return longs[kth-s-1];
}
return getUpMedian(shorts,0,s-1,longs,kth-s,kth-1);
}
//返回a1[s1....e1]和a2[s2....e2]中的上中位数,要满足e1-s1==e2-s2
public static int getUpMedian(int[] a1, int s1, int e1, int[] a2, int s2, int e2) {
int mid1 = 0; //a1数组中上中位数的位置
int mid2 = 0; //a2数组中上中位数的位置
int offset = 0;
while (s1 < e1) {
mid1 = (s1 + e1) / 2;
mid2 = (s2 + e2) / 2;
offset = ((e1 - s1 + 1) & 1) ^ 1; //用于判断数组的长度是奇数还是偶数,如果是奇数偏移量为0,偶数偏移量为1。
if (a1[mid1] > a2[mid2]) { //如果a1的中位数大于a2的中位数,那么a1大于中位数的数是不可能成为两个数组的上中位数,a2小于中位数的数是不可能成为两个数组的上中位数
e1 = mid1;
s2 = mid2 + offset;
} else if (a1[mid1] < a2[mid2]) { //如果a1的中位数小于a2的中位数,那么a1小于中位数的数是不可能成为两个数组的上中位数,a2大于中位数的数是不可能成为两个数组的上中位数
s1 = mid1 + offset;
e2 = mid2;
} else { //如果两个数组的上中位数相等,返回其中一个即可,就是两个数组合并之后的上中位数
return a1[mid1];
}
}
return Math.min(a1[s1], a2[s2]);
}
// For test, this method is inefficient but absolutely right
public static int[] getSortedAllArray(int[] arr1, int[] arr2) {
if (arr1 == null || arr2 == null) {
throw new RuntimeException("Your arr is invalid!");
}
int[] arrAll = new int[arr1.length + arr2.length];
int index = 0;
for (int i = 0; i != arr1.length; i++) {
arrAll[index++] = arr1[i];
}
for (int i = 0; i != arr2.length; i++) {
arrAll[index++] = arr2[i];
}
Arrays.sort(arrAll);
return arrAll;
}
public static int[] generateSortedArray(int len, int maxValue) {
int[] res = new int[len];
for (int i = 0; i != len; i++) {
res[i] = (int) (Math.random() * (maxValue + 1));
}
Arrays.sort(res);
return res;
}
}
题目五
public class BuildingOutline {
// 描述高度变化的对象
public static class Node {
public int x; // x轴上的值
public boolean isAdd;// true为加入,false为删除
public int h; // 高度
public Node(int x, boolean isAdd, int h) {
this.x = x;
this.isAdd = isAdd;
this.h = h;
}
}
// 排序的比较策略
// 1,第一个维度的x值从小到大。
// 2,如果第一个维度的值相等,看第二个维度的值,“加入”排在前,“删除”排在后
// 3,如果两个对象第一维度和第二个维度的值都相等,则认为两个对象相等,谁在前都行。
public static class MyComparator implements Comparator<Node> {
@Override
public int compare(Node o1, Node o2) { //o1是当前对象,o2是要比较的对象,如果返回正数,那就要把o1排在o2后面
if (o1.x != o2.x) { //x升序排列
return o1.x - o2.x;
}
if (o1.isAdd != o2.isAdd) { //如果x一样,按照isAdd是true在前,false在后的原则
return o1.isAdd ? -1 : 1; //返回负数,说明放在前面
}
return 0;
}
}
// 全部流程的主方法
public static List<List<Integer>> buildingOutline(int[][] matrix) {
Node[] nodes = new Node[matrix.length * 2]; //需要把matrix中例如[2,5,6]拆分成[2,true,6]和[5,false,6]的形式,故长度是matrix.length的2倍
// 每一个大楼轮廓数组,产生两个描述高度变化的对象
for (int i = 0; i < matrix.length; i++) {
nodes[2 * i] = new Node(matrix[i][0], true, matrix[i][2]);
nodes[2 * i + 1] = new Node(matrix[i][1], false, matrix[i][2]);
}
//对nodes数组进行排序
Arrays.sort(nodes, new MyComparator());
TreeMap<Integer, Integer> mapHeightTimes = new TreeMap<>(); //key:某个高度 value:某个高度出现的次数
TreeMap<Integer, Integer> mapXvalueHeight = new TreeMap<>(); //key:坐标 value:坐标对应的最大高度
for (int i = 0; i < nodes.length; i++) {
if (nodes[i].isAdd) { //如果当前节点是加入
if (!mapHeightTimes.containsKey(nodes[i].h)) { //前面没出现过,则加入mapHeightTimes,次数为1
mapHeightTimes.put(nodes[i].h, 1);
} else { //如果前面加入过,直接次数加一
mapHeightTimes.put(nodes[i].h, mapHeightTimes.get(nodes[i].h) + 1);
}
} else { //如果当前节点是删除操作
if (mapHeightTimes.get(nodes[i].h) == 1) { //如果当前高度在mapHeightTimes中只出现过一次,这次删完就没了,从mapHeightTimes中移除
mapHeightTimes.remove(nodes[i].h);
} else { //如果当前高度在mapHeightTimes中出现过多次,次数减一即可
mapHeightTimes.put(nodes[i].h, mapHeightTimes.get(nodes[i].h) - 1);
}
}
if (!mapHeightTimes.isEmpty()) { //如果mapHeightTimes不为空,则当前坐标的最大高度就是mapHeightTimes的最后一个key
mapXvalueHeight.put(nodes[i].x, mapHeightTimes.lastKey());
} else { //如果mapHeightTimes为空,则当前坐标的最大高度就是0
mapXvalueHeight.put(nodes[i].x, 0);
}
}
// res为结果数组,每一个List<Integer>代表一个轮廓线,有开始位置,结束位置,高度,一共三个信息
List<List<Integer>> res = new ArrayList<>();
// 一个新轮廓线的开始位置
int start = 0;
// 之前的最大高度
int preHeight = 0;
// 根据mapXvalueHeight生成res数组
for (Map.Entry<Integer, Integer> entry : mapXvalueHeight.entrySet()) {
//当前位置
int cur = entry.getKey();
//当前位置的最大高度
int curHeight = entry.getValue();
if (preHeight != curHeight) {
if (preHeight != 0) {
res.add(new ArrayList<>(Arrays.asList(start, cur, preHeight)));
}
start = cur;
preHeight = curHeight;
}
}
return res;
}
public static void main(String[] args) {
int[][] matrix = {{1, 3, 3},
{2, 4, 4},
{5, 6, 1}};
Iterator<List<Integer>> iterator = buildingOutline(matrix).iterator();
while(iterator.hasNext()){
System.out.println(iterator.next());
}
}
}
题目六
public class LongestSumSubArrayLengthInPositiveArray {
/*
思路:滑动窗口,如果sum<k,r++;如果sum>k,l++;如果sum==k,收集一下答案,l++
*/
public static int getMaxLength(int[] arr, int k) {
if (arr == null || arr.length == 0 || k <= 0) {
return 0;
}
int ans = 0;
int l = 0, r = 0, sum = arr[0];
while (r < arr.length) {
if (sum < k) {
r++;
if (r == arr.length) {
break;
}
sum += arr[r];
} else if (sum > k) {
sum -= arr[l++];
} else {
ans = Math.max(ans, r - l + 1); //由于是左闭右闭区间,所以长度是r-l+1
sum -= arr[l++];
}
}
return ans;
}
public static int[] generatePositiveArray(int size) {
int[] result = new int[size];
for (int i = 0; i != size; i++) {
result[i] = (int) (Math.random() * 10) + 1;
}
return result;
}
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int len = 20;
int k = 15;
int[] arr = generatePositiveArray(len);
printArray(arr);
System.out.println(getMaxLength(arr, k));
}
}
题目七
public class LongestLessSumSubArrayLength {
public static int maxLengthAwesome(int[] arr, int k) {
if(arr==null||arr.length==0){
return 0;
}
int[] minSum=new int[arr.length]; //minSum[i]的意思是从i位置开始往后的最小相加和
int[] minSumEnd=new int[arr.length]; //minSumEnd[i]的意思是从i位置开始往后的最小相加和的右边界
int mslength=minSum.length;
minSum[mslength-1]=arr[mslength-1]; //最后一个位置的最小相加和就是他自己
minSumEnd[mslength-1]=mslength-1; //最后一个位置的最小相加和的右边界就是最后一个位置
for(int i=mslength-2;i>=0;i--){
if(arr[i]+minSum[i+1]<=arr[i]){ //如果当前数加上其后一个数的最小相加和小于等于当前数
minSum[i]=arr[i]+minSum[i+1]; // 当前数的最小相加和就是当前数加上其后一个数的最小相加和
minSumEnd[i]=minSumEnd[i+1]; // 当前数的最小相加和的右边界就是其后一个数的最小相加和的右边界
}else{ //如果当前数加上其后一个数的最小相加和大于等于当前数
minSum[i]=arr[i]; //当前数的最小相加和就是当前数
minSumEnd[i]=i; //当前数的最小相加和的右边界就是当前数的位置
}
}
int ans=0; //记录最长的子数组长度
int sum=0;
int end=0;
// i是窗口的最左的位置,end是窗口最右位置的下一个位置
for(int i=0;i<mslength;i++){
// while循环结束之后:
// 1) 如果以i开头的情况下,累加和<=k的最长子数组是arr[i..end-1],看看这个子数组长度能不能更新res;
// 2) 如果以i开头的情况下,累加和<=k的最长子数组比arr[i..end-1]短,更新还是不更新res都不会影响最终结果;
while (end<arr.length&&sum+minSum[end]<=k){ //如果sum+minSum[i]<=k,则可以继续往右加
sum+=minSum[end];
end=minSumEnd[end]+1;
}
//退出循环,说明已经找到了最长子数组的相加和<=k,更新ans
ans=Math.max(ans,end-i);
if(end>i){ //说明窗口内还有数
sum-=arr[i];
}else{ // 窗口内已经没有数了,说明从i开头的所有子数组累加和都不可能<=k
end=i+1;
}
}
return ans;
}
public static int maxLength(int[] arr, int k) {
int[] h = new int[arr.length + 1];
int sum = 0;
h[0] = sum; //h数组中0位置是没用的,h[i]的意思是[0...i]范围中的最大累加和
for (int i = 0; i != arr.length; i++) {
sum += arr[i];
h[i + 1] = Math.max(sum, h[i]);
}
sum = 0;
int res = 0;
int pre = 0;
int len = 0;
for (int i = 0; i != arr.length; i++) {
sum += arr[i];
pre = getLessIndex(h, sum - k); //找到累加和等于sum-k的最左位置
len = pre == -1 ? 0 : i - pre + 1; //如果没找到,则这个子数组不存在,长度为0
res = Math.max(res, len);
}
return res;
}
public static int getLessIndex(int[] arr, int num) {
int low = 0;
int high = arr.length - 1;
int mid = 0;
int res = -1;
while (low <= high) {
mid = (low + high) / 2;
if (arr[mid] >= num) {
res = mid;
high = mid - 1;
} else {
low = mid + 1;
}
}
return res;
}
// for test
public static int[] generateRandomArray(int len, int maxValue) {
int[] res = new int[len];
for (int i = 0; i != res.length; i++) {
res[i] = (int) (Math.random() * maxValue) - (maxValue / 3);
}
return res;
}
public static void main(String[] args) {
for (int i = 0; i < 10000000; i++) {
int[] arr = generateRandomArray(10, 20);
int k = (int) (Math.random() * 20) - 5;
if (maxLengthAwesome(arr, k) != maxLength(arr, k)) {
System.out.println("oops!");
}
}
}
}
题目八
//本题是一道nim博弈论问题
// 保证arr是正数数组
public static boolean printWinner(int[] arr){
int eor=0;
for (int i : arr) {
eor^=i;
}
if(eor==0){ //异或和等于0,后手赢
return false;
}else{ //异或和不等于0,先手赢
return true;
}
}
题目九
public class NumberAndString {
//chs所给字符数组,n所给正数
public static String getString(char[] chs, int n) {
if(chs==null||chs.length==0||n<1){
return "";
}
int cur=1;
int base=chs.length; //chs多长就是多少进制
int len=0;
while(n>=cur){ //在满足res数组上每一位数都是1的前提下,res数组多长
n-=cur;
len++;
cur*=base;
}
char[] res=new char[len];
int index=0;
int nCur=0;
do{
cur/=base; //由于在上面while循环的时候cur多乘了一个base,这里还原
nCur=n/cur; //n中还包含多少个cur
res[index++] = getKthCharAtChs(chs, nCur + 1); //由于上面while循环的时候就已经有一个了,所以这里是nCur+1
n %= cur;
}while (index != res.length);
return String.valueOf(res);
}
//获取chs数组中第k个字符
public static char getKthCharAtChs(char[] chs, int k) {
if (k < 1 || k > chs.length) {
return 0;
}
return chs[k - 1];
}
public static int getNum(char[] chs, String str) {
if (chs == null || chs.length == 0) {
return 0;
}
char[] strc = str.toCharArray();
int base = chs.length;
int cur = 1;
int res = 0;
for (int i = strc.length - 1; i != -1; i--) {
res += getNthFromChar(chs, strc[i]) * cur;
cur *= base;
}
return res;
}
//获取ch字符是chs数组中第几个字符
public static int getNthFromChar(char[] chs, char ch) {
int res = -1;
for (int i = 0; i != chs.length; i++) {
if (chs[i] == ch) {
res = i + 1;
break;
}
}
return res;
}
public static void main(String[] args) {
char[] chs = { 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K',
'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W',
'X', 'Y', 'Z' };
int len = 1;
String res = "";
for (int i = 1; i != 705; i++) {
res = getString(chs, i);
if (res.length() != len) {
len = res.length();
System.out.println("================");
}
System.out.print(res + " ");
if (i % chs.length == 0) {
System.out.println();
}
}
System.out.println();
System.out.println("========================");
int testNum = 78128712;
System.out.println(getNum(chs, getString(chs, testNum)));
String testStr = "BZZA";
System.out.println(getString(chs, getNum(chs, testStr)));
}
}
题目十
package dailytrain;
public class SnakeGame {
//暴力递归
public static int walk1(int[][] matrix) {
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int ans = Integer.MIN_VALUE;
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
ans=Math.max(ans,Math.max(process1(matrix,i,j).no,process1(matrix,i,j).yes));
}
}
return ans;
}
public static class Info {
public int no; //记录没有用过超能力的最大长度
public int yes; //记录用过超能力的最大长度
public Info(int no, int yes) {
this.no = no;
this.yes = yes;
}
}
//从最左侧出发(具体哪个位置不关心),当前到达(row,col)
//在这个旅程中
//no,一次能力也没有能够达到的最大长度(如果为负数,说明是个无效解)
//yes,用过一次能力能够达到的最大长度(如果为负数,说明是个无效解)
public static Info process1(int[][] matrix, int row, int col) {
if (col == 0) { //最左侧的时候 base case
return new Info(matrix[row][0], -matrix[row][0]);
}
int preNo=-1; //记录达到(row,col)为之前没有用过的超能力的最大路径
int preYes=-1; //记录达到(row,col)为之前用过的超能力的最大路径
if(row>0){ //说明有左上角的路径
Info leftUp=process1(matrix,row-1,col-1);
if(leftUp.no>=0){ //如果左上角的路径不是一个无效解
preNo=Math.max(preNo,leftUp.no);
}
if(leftUp.yes>=0){
preYes=Math.max(preYes,leftUp.yes);
}
}
//有左侧的路径
Info left=process1(matrix,row,col-1);
if(left.no>=0){
preNo=Math.max(preNo,left.no);
}
if(left.yes>=0){
preYes=Math.max(preYes,left.yes);
}
if(row<matrix.length-1){ //有左下角的路径
Info leftDown=process1(matrix,row+1,col-1);
if(leftDown.no>=0){
preNo=Math.max(preNo,leftDown.no);
}
if(leftDown.yes>=0){
preYes=Math.max(preYes,leftDown.yes);
}
}
//开始处理当前节点的信息
int no=-1;
int yes=-1;
if(preNo>=0){ //如果达到当前节点前,没有用过超能力的最大长度是一个有效解
no=preNo+matrix[row][col]; //当前节点也不使用超能力
yes=preNo-matrix[row][col]; //当前节点使用超能力
}
if(preYes>=0){ //如果达到当前节点前,用过超能力的最大长度是一个有效解
yes=Math.max(yes,preYes+matrix[row][col]); //前面已经使用过超能力,当前节点不能再使用了
}
return new Info(no,yes);
}
//记忆化搜索
public static int walk2(int[][] matrix){
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int ans = Integer.MIN_VALUE;
Info[][] dp=new Info[matrix.length][matrix[0].length];
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length; j++) {
ans=Math.max(ans,Math.max(process2(matrix,i,j,dp).no,process2(matrix,i,j,dp).yes));
}
}
return ans;
}
public static Info process2(int[][] matrix, int row, int col,Info[][] dp) {
if(dp[row][col]!=null){
return dp[row][col];
}
if (col == 0) { //最左侧的时候 base case
dp[row][0] = new Info(matrix[row][0], -matrix[row][0]);
return dp[row][0];
}
int preNo=-1; //记录达到(row,col)为之前没有用过的超能力的最大路径
int preYes=-1; //记录达到(row,col)为之前用过的超能力的最大路径
if(row>0){ //说明有左上角的路径
Info leftUp=process2(matrix,row-1,col-1,dp);
if(leftUp.no>=0){ //如果左上角的路径不是一个无效解
preNo=Math.max(preNo,leftUp.no);
}
if(leftUp.yes>=0){
preYes=Math.max(preYes,leftUp.yes);
}
}
//有左侧的路径
Info left=process2(matrix,row,col-1,dp);
if(left.no>=0){
preNo=Math.max(preNo,left.no);
}
if(left.yes>=0){
preYes=Math.max(preYes,left.yes);
}
if(row<matrix.length-1){ //有左下角的路径
Info leftDown=process2(matrix,row+1,col-1,dp);
if(leftDown.no>=0){
preNo=Math.max(preNo,leftDown.no);
}
if(leftDown.yes>=0){
preYes=Math.max(preYes,leftDown.yes);
}
}
//开始处理当前节点的信息
int no=-1;
int yes=-1;
if(preNo>=0){ //如果达到当前节点前,没有用过超能力的最大长度是一个有效解
no=preNo+matrix[row][col]; //当前节点也不使用超能力
yes=preNo-matrix[row][col]; //当前节点使用超能力
}
if(preYes>=0){ //如果达到当前节点前,用过超能力的最大长度是一个有效解
yes=Math.max(yes,preYes+matrix[row][col]); //前面已经使用过超能力,当前节点不能再使用了
}
dp[row][col] = new Info(no,yes);
return dp[row][col];
}
//动态规划
public static int walk3(int[][] matrix){
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return 0;
}
int ans = Integer.MIN_VALUE;
Info[][] dp=new Info[matrix.length][matrix[0].length];
for(int i=0;i<dp.length;i++){ //第一列是基础
dp[i][0]=new Info(matrix[i][0],-matrix[i][0]);
}
//从左往右,从上往下
for(int col=1;col<dp[0].length;col++){
for(int row=0;row<dp.length;row++){
int preNo=-1; //记录达到(row,col)为之前没有用过的超能力的最大路径
int preYes=-1; //记录达到(row,col)为之前用过的超能力的最大路径
if(row>0){ //需要考虑左上角位置
if(dp[row-1][col-1].no>=0){
preNo=Math.max(preNo,dp[row-1][col-1].no);
}
if(dp[row-1][col-1].yes>=0){
preYes=Math.max(preYes,dp[row-1][col-1].yes);
}
}
//考虑左侧位置
if(dp[row][col-1].no>=0){
preNo=Math.max(preNo,dp[row][col-1].no);
}
if(dp[row][col-1].yes>=0){
preYes=Math.max(preYes,dp[row][col-1].yes);
}
if(row<dp.length-1){ //考虑左下角位置
if(dp[row+1][col-1].no>=0){
preNo=Math.max(preNo,dp[row+1][col-1].no);
}
if(dp[row+1][col-1].yes>=0){
preYes=Math.max(preYes,dp[row+1][col-1].yes);
}
}
int no=-1;
int yes=-1;
if(preNo>=0){
no=preNo+matrix[row][col];
yes=preNo-matrix[row][col];
}
if(preYes>=0){
yes=Math.max(yes,preYes+matrix[row][col]);
}
dp[row][col]=new Info(no,yes);
ans=Math.max(ans,Math.max(no,yes));
}
}
return ans;
}
public static void main(String[] args) {
int[][] matrix = { { 1, -4, 10 }, { 3, -2, -1 }, { 2, -1, 0 }, { 0, 5, -2 } };
System.out.println(walk1(matrix));
System.out.println(walk2(matrix));
System.out.println(walk3(matrix));
}
}
题目十一
public class ExpressionCompute {
public static int getValue(String str) {
return value(str.toCharArray(), 0)[0];
}
/**
* 用于计算一个数学表达式的计算结果
* @param chars 数学表达式的数组
* @param i 从哪个位置开始计算
* @return 返回一个int数组,int[0]:计算到当前位置的计算结果,int[1]:下次应该从哪个位置开始计算
*/
public static int[] value(char[] chars, int i) {
LinkedList<String> que = new LinkedList<>();
int pre = 0;
int[] bra = null;
while (i < chars.length && chars[i] != ')') {
if (chars[i] >= '0' && chars[i] <= '9') { // 如果当前字符是0~9
pre = pre * 10 + chars[i++] - '0'; // 将结果保存到pre变量中
} else if (chars[i] != '(') { // 如果当前字符不是(,说明碰到了运算符
addNum(que, pre);
que.addLast(String.valueOf(chars[i++])); // 把运算符号压入栈
pre = 0;
} else { //当前符号是左括号
bra = value(chars, i + 1); // 递归调用,bra是一个括号内计算出来的返回值
pre = bra[0]; // 一个括号内的计算结果
i = bra[1] + 1; // 下一次计算的位置
}
}
addNum(que, pre);
return new int[]{getNum(que), i};
}
/**
* 计算一部分数学表达式的结果再压回队列
* @param que 双端队列
* @param num 当前要计算的数字
*/
public static void addNum(LinkedList<String> que, int num) {
if (!que.isEmpty()) {
int cur = 0;
String pop = que.pollLast(); // 弹出的肯定是一个运算符
if (pop.equals("+") || pop.equals("-")) { // 如果弹出的元素是加号或者是减号,可以先不做运算,把运算符再压回去
que.addLast(pop);
} else { // 如果弹出的元素是乘号或者是除号,再弹一个数出来做计算,计算完再压回去
cur = Integer.parseInt(que.pollLast());
num = pop.equals("*") ? (cur * num) : (cur / num);
}
}
que.addLast(String.valueOf(num)); // 栈为空,直接把num压进去即可,栈非空,把计算完的结果压进去
}
// 此时队列中只剩下数字与加减号了,所以从队列头部弹出开始计算最终的结果
public static int getNum(LinkedList<String> que) {
int res = 0;
boolean add = true;
String cur = null;
int num = 0;
while (!que.isEmpty()) {
cur = que.pollFirst(); // 从队列头部弹出元素
if (cur.equals("+")) {
add = true;
} else if (cur.equals("-")) {
add = false;
} else { // 是一个数字
num = Integer.valueOf(cur);
res += add ? num : (-num);
}
}
return res;
}
public static void main(String[] args) {
String exp = "48*((70-65)-43)+8*1";
System.out.println(getValue(exp));
exp = "4*(6+78)+53-9/2+45*8";
System.out.println(getValue(exp));
exp = "10-5*3";
System.out.println(getValue(exp));
exp = "-3*4";
System.out.println(getValue(exp));
exp = "3+1*4";
System.out.println(getValue(exp));
}
}
题目十二
public class LCSubstring {
public static String lcst1(String str1, String str2) {
if (str1 == null || str2 == null || str1.equals("") || str2.equals("")) {
return "";
}
char[] chars1 = str1.toCharArray();
char[] chars2 = str2.toCharArray();
int[][] dp = getdp(chars1, chars2); //dp[i][j]的意思是以chars1[i]和chars2[j]结尾的最长公共子串
int end = 0; //记录最长公共子串的结尾位置
int ans = -1;
for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[0].length; j++) {
if (ans < dp[i][j]) {
ans = dp[i][j];
end = i; //这里记录i或者j都可以
}
}
}
return str1.substring(end - ans + 1, end + 1);
}
public static int[][] getdp(char[] str1, char[] str2) {
int[][] dp = new int[str1.length][str2.length];
for (int i = 0; i < dp[0].length; i++) { //处理第一行,如果str2中的字符跟str1[0]的字符相等,则最长公共子串就为1,否则就为0
dp[0][i] = str1[0] == str2[i] ? 1 : 0;
}
for (int i = 0; i < dp.length; i++) { //处理第一列,如果str1中的字符跟str2[0]的字符相等,则最长公共子串就为1,否则就为0
dp[i][0] = str1[i] == str2[0] ? 1 : 0;
}
//从上向下,从左往右
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length; j++) {
if (str1[i] == str2[j]) { //只有两个字符相等才需要计算,其余情况都为0
dp[i][j] = dp[i - 1][j - 1] + 1;
}
}
}
return dp;
}
public static String lcst2(String str1, String str2) {
if (str1 == null || str2 == null || str1.equals("") || str2.equals("")) {
return "";
}
char[] chs1 = str1.toCharArray();
char[] chs2 = str2.toCharArray();
int row = 0;
int col = chs2.length - 1;
int end = 0; //最长公共子串的结尾位置
int max = 0; //最长公共子串长度
while (row < chs1.length) {
int i = row;
int j = col;
int len = 0;
while (i < chs1.length && j < chs2.length) { //一开始的起点在右上角,按照主对角线的方向更新,左上往右下
if (chs1[i] != chs2[j]) {
len = 0;
} else {
len++;
}
if (len > max) {
max = len;
end = i;
}
i++;
j++;
}
//一条斜线更新完,起点需要更新,起点位置的更新是从右上到左上再到左下
if (col > 0) {
col--;
}else{
row++;
}
}
return str1.substring(end - max + 1, end + 1);
}
public static void printArray(int[][] dp) {
for (int i = 0; i < dp.length; i++) {
for (int j = 0; j < dp[0].length; j++) {
System.out.print(dp[i][j] + " ");
}
System.out.println();
}
}
public static void main(String[] args) {
String str1 = "ABC1234567DEFG";
String str2 = "HIJKL1234567MNOP";
System.out.println(lcst1(str1, str2));
System.out.println(lcst2(str1, str2));
}
}
题目十三
public class LCSubsequence {
public static String lcse(String str1, String str2) {
if (str1 == null || str2 == null || str1.equals("") || str2.equals("")) {
return "";
}
char[] chars1 = str1.toCharArray();
char[] chars2 = str2.toCharArray();
int[][] dp = getdp(chars1, chars2); //dp[i][j]的意思是chars1[0...i]和chars2[0...j]范围上的最长子序列长度
int m = chars1.length - 1;
int n = chars2.length - 1;
char[] res = new char[dp[m][n]]; //dp[m][n]是str1和str2最长公共子序列的长度
int index = res.length - 1;
while (index >= 0) {
if (n > 0 && dp[m][n] == dp[m][n - 1]) { //如果dp[m][n]==dp[m][n-1],说明chars1[m]和chars2[n]不相等
n--;
} else if (m > 0 && dp[m][n] == dp[m - 1][n]) { //如果dp[m][n]==dp[m-1][n],说明chars1[m]和chars2[n]不相等
m--;
} else { //dp[m][n]!=dp[m][n-1]或者dp[m][n]!=dp[m-1][n]说明chars1[m]或者chars2[n]是个公共字符
res[index--] = chars1[m];
m--;
n--;
}
}
return String.valueOf(res);
}
public static int[][] getdp(char[] chars1, char[] chars2) {
int[][] dp = new int[chars1.length][chars2.length];
dp[0][0] = chars1[0] == chars2[0] ? 1 : 0;
for (int i = 1; i < dp[0].length; i++) {
dp[0][i] = Math.max(chars1[0] == chars2[i] ? 1 : 0, dp[0][i - 1]);
}
for (int i = 1; i < dp.length; i++) {
dp[i][0] = Math.max(chars1[i] == chars2[0] ? 1 : 0, dp[i - 1][0]);
}
//从上往下,从左往右
//分为4种情况:
//第一种情况:chars1[i]==chars2[j],dp[i][j]=dp[i-1][j-1]+1
//第二种情况:chars1[i]!=chars2[j],以i结尾为准,dp[i][j]=dp[i][j-1]
//第三种情况:chars1[i]!=chars2[j],以j结尾为准,dp[i][j]=dp[i-1][j]
//第四种情况:chars1[i]!=chars2[j],既不以i也不以j为准,dp[i][j]=dp[i-1][j-1]
//上述四种情况选取最大的
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length; j++) {
if (chars1[i] == chars2[j]) {
dp[i][j] = dp[i - 1][j - 1] + 1;
} else {
dp[i][j] = Math.max(Math.max(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]);
}
}
}
return dp;
}
public static void main(String[] args) {
String str1 = "A1BC2D3EFGH45I6JK7LMN";
String str2 = "12OPQ3RST4U5V6W7XYZ";
System.out.println(lcse(str1, str2));
}
}
题目十四
public class MinBoat {
//双指针法,两边向中间滑动
public static int minBoat1(int[] arr, int weight) {
if (arr == null || arr.length == 0) {
return 0;
}
Arrays.sort(arr); //将数组升序排列
int ans = 0;
//左右指针
int leftIndex = 0;
int rightIndex = arr.length - 1;
while (leftIndex <= rightIndex) {
if (arr[leftIndex] + arr[rightIndex] <= weight) { //最轻的和最重的能够做一条船
leftIndex++;
rightIndex--;
ans++;
} else { //最轻的和最重的不能做一条船,那就让最重的自己做一条
ans++;
rightIndex--;
}
}
return ans;
}
public static int minBoat2(int[] arr, int weight) {
if (arr == null || arr.length == 0) {
return 0;
}
Arrays.sort(arr);
int limit = weight / 2;
int leftIndex = -1;
for (int i = arr.length - 1; i >= 0; i--) {
if (arr[i] <= limit) {
leftIndex = i;
break;
}
}
if (leftIndex == -1) { //如果数组中任意两个数都不能做一艘船,则需要数组长度的船
return arr.length;
} else if (leftIndex == arr.length - 1) { //如果数组中任意两个数都可以做一艘船,则只需要数组长度的一半的船
return (arr.length - 1) / 2 + 1;
}
int count1 = 0, count2 = 0, count3 = 0; //统计三种不同情况,count1记录两两做一艘船,count2也是记录两两做一艘船,但是每一个数都小于等于limit,count3记录一人做一艘船
int rightIndex = leftIndex + 1;
while (leftIndex >= 0 && rightIndex < arr.length) {
if (arr[leftIndex] + arr[rightIndex] > weight) { //如果当前两个人不能做一艘船,但是arr[leftIndex]<=limit,count2++
count2++;
leftIndex--;
} else { //如果当前两个人能做一艘船
count1++;
leftIndex--;
rightIndex++;
}
}
if (rightIndex < arr.length) { //左边的数处理完了,右边还有剩余,只能一人做一艘
count3 += arr.length - rightIndex;
} else if (leftIndex >= 0) { //左边数还有,右边处理完了,左边的数都可以两两做一艘
count3 += (leftIndex + 1) / 2;
}
return count1 + count3 + (count2 == 0 ? 0 : ((count2 - 1) / 2 + 1));
}
// 请保证arr有序
public static int minBoat3(int[] arr, int weight) {
if (arr == null || arr.length == 0) {
return 0;
}
Arrays.sort(arr);
int lessR = -1;
for (int i = arr.length - 1; i >= 0; i--) {
if (arr[i] <= (weight / 2)) {
lessR = i;
break;
}
}
if (lessR == -1) { //如果数组中任意两个数都不能做一艘船,则需要数组长度的船
return arr.length;
}
int lessIndex = lessR; //小于等于limit的右边界
int moreIndex = lessR + 1; //大于等于limit的左边界
int lessUnused = 0;
while (lessIndex >= 0) {
int solved = 0; //两人做一艘船的个数,一个人小于等于limit,一个人大于limit
while (moreIndex < arr.length
&& arr[lessIndex] + arr[moreIndex] <= weight) {
moreIndex++;
solved++;
}
if (solved == 0) { //如果arr[lessIndex] + arr[moreIndex]<=weight的方案没有,则lessUnused++,lessIndex--
lessUnused++;
lessIndex--;
} else { //如果有arr[lessIndex] + arr[moreIndex]<=weight的方案,则lessIndex向左移solved
lessIndex = Math.max(-1, lessIndex - solved); //什么时候取到-1, 只有当满足两两一艘船时,左边的数不够右边的数
}
}
int lessAll = lessR + 1; //小于等于limit的个数
int lessUsed = lessAll - lessUnused; //小于等于limit的中用于两两一艘船的个数
int moreUnsolved = arr.length - lessR - 1 - lessUsed; //大于limit的但是不能两两一艘船的,只能一人一艘船
return lessUsed + ((lessUnused + 1) >> 1) + moreUnsolved;
}
//随机生成n长的数组
public static int[] getArray(int n) {
int[] arr = new int[n];
Random r = new Random();
for (int i = 0; i < n; i++) {
arr[i] = r.nextInt(19)+1;
}
return arr;
}
public static void main(String[] args) {
// int[] arr = {3, 3, 3, 4, 4, 4, 5, 5, 5, 6, 6, 6, 7, 7, 7, 8, 8};
int weight = 10;
for (int i = 0; i < 1000000; i++) {
int[] arr = getArray(20);
if (minBoat1(arr, weight) != minBoat2(arr, weight) || minBoat1(arr, weight) != minBoat3(arr, weight) || minBoat2(arr, weight) != minBoat3(arr, weight)) {
System.out.println(Arrays.toString(arr));
System.out.println(minBoat1(arr, weight));
System.out.println(minBoat2(arr, weight));
System.out.println(minBoat3(arr, weight));
System.out.println("fail");
return;
}
}
System.out.println("success");
}
}
题目十五
public class PalindromeSubsequence {
public static int maxLen1(String str) {
if (str == null || str.length() == 0) {
return 0;
}
char[] chars = str.toCharArray();
int[][] dp = new int[chars.length][chars.length]; //dp[i][j]的意思是i....j范围上的最长回文子序列,整个表格中左下半部分是不需要填的
for (int i = 0; i < dp.length; i++) { //对角线上都是1
dp[i][i] = 1;
}
//从下往上,从左往右填,如果chars[i]==chars[j],则dp[i][j]=dp[i+1][j-1]+2,如果不等,则dp[i][j]=Math.max(dp[i+1][j],dp[i+1][j-1],dp[i][j-1])
for (int i = dp.length - 2; i >= 0; i--) {
for (int j = i + 1; j < dp.length; j++) {
if (chars[i] == chars[j]) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], Math.max(dp[i + 1][j - 1], dp[i][j - 1]));
}
}
}
return dp[0][dp.length - 1];
}
public static void main(String[] args) {
String test = "A1BC2D33FG2H1I";
System.out.println(maxLen1(test));
// System.out.println(maxLen2(test));
}
}
题目十六
public class PalindromeMinAdd {
public static String getPalindrome1(String str) {
if (str == null || str.length() == 0) {
return str;
}
char[] chars = str.toCharArray();
int[][] dp = getDp(chars);
int add=dp[0][chars.length-1]; //至少需要添加字符的个数
char[] newStr=new char[chars.length+add];
int i=0;
int j=chars.length-1;
int resl=0;
int resr=newStr.length-1;
while(i<=j){
if(chars[i]==chars[j]){ //如果原数组的左右指针指向的字符相等,直接复制
newStr[resl++]=chars[i++];
newStr[resr--]=chars[j--];
}else if(dp[i][j-1]<dp[i+1][j]){ //如果两个字符不等,dp[i][j-1]<dp[i+1][j]说明dp[i][j]是来自于dp[i][j-1],是在i位置前面添了一个chars[j]
newStr[resl++]=chars[j];
newStr[resr--]=chars[j--];
}else{ //dp[i][j-1]>dp[i+1][j]说明dp[i][j]是来自于dp[i+1][j],是在j位置后面添了一个chars[i]
newStr[resl++]=chars[i];
newStr[resr--]=chars[i++];
}
}
return Arrays.toString(newStr);
}
public static int[][] getDp(char[] chars) {
int n = chars.length;
int[][] dp = new int[n][n]; //dp[i][j]的意思是i...j范围上最少需要添加多少个字符才是回文字符串
for (int i = 0; i < n; i++) { //对角线上全是0,因为一个字符的时候不管怎么样都是回文字符串,不需要添加任何字符
dp[i][i] = 0;
}
//从下往上,从左往右。如果chars[i]==chars[j],说明首尾字符相等,只需要让i+1...j-1范围上是回文字符串即可,故dp[i][j]=dp[i+1][j-1];
// 如果不等,有两种情况,第一种让i...j-1上是回文字符串,再在开头添加一个chars[j]就是回文字符串,或者让i+1...j上是回文字符串,再在末尾添加一个chars[i]就是回文字符串,故dp[i][j]=Math.min(dp[i+1][j],dp[i][j-1])+1
for (int i = n - 2; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if (chars[i] == chars[j]) {
dp[i][j] = dp[i + 1][j - 1];
} else {
dp[i][j] = Math.min(dp[i + 1][j], dp[i][j - 1]) + 1;
}
}
}
return dp;
}
public static void main(String[] args) {
String str = "AB1CD2EFG3H43IJK2L1MN";
System.out.println(getPalindrome1(str));
}
}
题目十七
package dailytrain;
public class PalindromeMinCut {
public static int minCut(String str) {
if (str == null || str.length() == 0) {
return 0;
}
char[] chars = str.toCharArray();
int len = chars.length;
int[] dp = new int[len + 1]; //dp[i]的意思是i...len范围上最少需要切割几刀才全部是回文字符串
dp[len] = -1;
boolean[][] p = new boolean[len][len]; //p[i][j]的意思是i...j范围上是不是回文字符串
//从后往前
for (int i = len - 1; i >= 0; i--) {
dp[i] = Integer.MAX_VALUE;
for (int j = i; j < len; j++) {
if (chars[i] == chars[j] && (j - i < 2 || p[i + 1][j - 1])) { //如果chars[i]==chars[j]并且只有一个字符时,p[i][j]=true;或者chars[i]==chars[j]并且p[i+1][j-1]是回文字符串,则dp[i][j]=true
p[i][j] = true;
dp[i] = Math.min(dp[i], dp[j + 1] + 1); //如果i...j范围上是回文字符串,则切割次数就在j...len范围上的切割次数加一即可
}
}
}
return dp[0];
}
// for test
public static String getRandomStringOnlyAToD(int len) {
int range = 'D' - 'A' + 1;
char[] charArr = new char[(int) (Math.random() * (len + 1))];
for (int i = 0; i != charArr.length; i++) {
charArr[i] = (char) ((int) (Math.random() * range) + 'A');
}
return String.valueOf(charArr);
}
public static void main(String[] args) {
int maxLen = 10;
int testTimes = 5;
String str = null;
for (int i = 0; i != testTimes; i++) {
str = getRandomStringOnlyAToD(maxLen);
System.out.print("\"" + str + "\"" + " : ");
System.out.println(minCut(str));
}
}
}
题目十八
package dailytrain;
public class PalindromeWays {
public static int way1(String str) {
if (str == null || str.length() == 0) {
return 0;
}
char[] chars = str.toCharArray();
int n = chars.length;
int[][] dp = new int[n][n]; //dp[i][j]的意思是i...j范围上有多少种方法让其变成回文字符串
//从下往上,从左往右,对角线的值都为1
/*
如何求dp[i][j]:
第一种情况:chars[i]==chars[j]
1.以i开头,不以j结尾:dp[i][j-1],但是dp[i][j-1]包括了不以j结尾的全部情况,即dp[i][j-1]包含了这里的1和3
2.不以i开头,以j结尾:dp[i+1][j],但是dp[i+1][j]包括了不以i开头的全部情况,即dp[i+1][j]包含了这里的2和3
3.不以i开头,不以j结尾:dp[i+1][j-1]
4.以i开头,以j结尾:就是在dp[i+1][j-1]的基础上加1即可,dp[i+1][j-1]+1
所以计算dp[i][j]是1+2-3+4 故:dp[i][j]=dp[i][j-1]+dp[i+1][j]-dp[i+1][j-1]+dp[i+1][j-1]+1=dp[i][j-1]+dp[i+1][j]+1
第二种情况:chars[i]!=chars[j]
1.以i开头,不以j结尾:dp[i][j-1],但是dp[i][j-1]包括了不以j结尾的全部情况,即dp[i][j-1]包含了这里的1和3
2.不以i开头,以j结尾:dp[i+1][j],但是dp[i+1][j]包括了不以i开头的全部情况,即dp[i+1][j]包含了这里的2和3
3.不以i开头,不以j结尾:dp[i+1][j-1]
所以计算dp[i][j]是1+2-3 故:dp[i][j]=dp[i][j-1]+dp[i+1][j]-dp[i+1][j-1]
*/
for (int i = 0; i < n; i++) {
dp[i][i] = 1;
}
for (int i = n - 2; i >= 0; i--) {
for (int j = i + 1; j < n; j++) {
if (chars[i] == chars[j]) {
dp[i][j] = dp[i][j - 1] + dp[i + 1][j] + 1;
} else {
dp[i][j] = dp[i][j - 1] + dp[i + 1][j] - dp[i + 1][j - 1];
}
}
}
return dp[0][n - 1];
}
public static int way2(String str) {
char[] s = str.toCharArray();
int len = s.length;
int[][] dp = new int[len + 1][len + 1];
for (int i = 0; i <= len; i++) {
dp[i][i] = 1;
}
for (int subLen = 2; subLen <= len; subLen++) {
for (int l = 1; l <= len - subLen + 1; l++) {
int r = l + subLen - 1;
dp[l][r] += dp[l + 1][r];
dp[l][r] += dp[l][r - 1];
if (s[l - 1] == s[r - 1])
dp[l][r] += 1;
else
dp[l][r] -= dp[l + 1][r - 1];
}
}
return dp[1][len];
}
public static void main(String[] args) {
System.out.println(way1("XXY"));
System.out.println(way1("XX"));
System.out.println(way1("ABA"));
System.out.println(way2("XXY"));
System.out.println(way2("XX"));
System.out.println(way2("ABA"));
}
}
题目十九
public class BFPRT {
public static int[] getMinKNumsByBFPRT(int[] arr, int k) {
if (k < 1 || k > arr.length) {
return arr;
}
int minKth = getMinKthByBFPRT(arr, k);
int[] res = new int[k];
int index = 0;
for (int i = 0; i != arr.length; i++) {
if (arr[i] < minKth) {
res[index++] = arr[i];
}
}
for (; index != res.length; index++) {
res[index] = minKth;
}
return res;
}
public static int getMinKthByBFPRT(int[] arr, int K) {
int[] copyArr = copyArray(arr);
return select(copyArr, 0, copyArr.length - 1, K - 1);
}
public static int[] copyArray(int[] arr) {
int[] res = new int[arr.length];
for (int i = 0; i != res.length; i++) {
res[i] = arr[i];
}
return res;
}
public static int select(int[] arr, int begin, int end, int i) {
if (begin == end) {
return arr[begin];
}
int pivot = medianOfMedians(arr, begin, end);
int[] pivotRange = partition(arr, begin, end, pivot);
if (i >= pivotRange[0] && i <= pivotRange[1]) {
return arr[i];
} else if (i < pivotRange[0]) {
return select(arr, begin, pivotRange[0] - 1, i);
} else {
return select(arr, pivotRange[1] + 1, end, i);
}
}
public static int medianOfMedians(int[] arr, int begin, int end) {
int num = end - begin + 1;
int offset = num % 5 == 0 ? 0 : 1;
int[] mArr = new int[num / 5 + offset];
for (int i = 0; i < mArr.length; i++) {
int beginI = begin + i * 5;
int endI = beginI + 4;
mArr[i] = getMedian(arr, beginI, Math.min(end, endI));
}
return select(mArr, 0, mArr.length - 1, mArr.length / 2);
}
public static int[] partition(int[] arr, int begin, int end, int pivotValue) {
int small = begin - 1;
int cur = begin;
int big = end + 1;
while (cur != big) {
if (arr[cur] < pivotValue) {
swap(arr, ++small, cur++);
} else if (arr[cur] > pivotValue) {
swap(arr, cur, --big);
} else {
cur++;
}
}
int[] range = new int[2];
range[0] = small + 1;
range[1] = big - 1;
return range;
}
public static int getMedian(int[] arr, int begin, int end) {
insertionSort(arr, begin, end);
int sum = end + begin;
int mid = (sum / 2) + (sum % 2);
return arr[mid];
}
public static void insertionSort(int[] arr, int begin, int end) {
for (int i = begin + 1; i != end + 1; i++) {
for (int j = i; j != begin; j--) {
if (arr[j - 1] > arr[j]) {
swap(arr, j - 1, j);
} else {
break;
}
}
}
}
public static void swap(int[] arr, int index1, int index2) {
int tmp = arr[index1];
arr[index1] = arr[index2];
arr[index2] = tmp;
}
public static void printArray(int[] arr) {
for (int i = 0; i != arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
public static void main(String[] args) {
int[] arr = { 6, 9, 1, 3, 1, 2, 2, 5, 6, 1, 3, 5, 9, 7, 2, 5, 6, 1, 9 };
printArray(getMinKNumsByBFPRT(arr, 10));
}
}
题目二十
public class SplitNumer {
public static int ways1(int n) {
if (n < 1) {
return 0;
}
return process(1, n);
}
//pre的意思当前分裂出来的数不能小于pre
//rest的意思是当前还剩余多少需要分裂
private static int process(int pre, int rest) {
if (rest == 0) { //如果剩余为0,说明有一种方法
return 1;
}
if (pre > rest) { //如果rest<pre说明没法分了,返回0
return 0;
}
int ways = 0;
for (int i = pre; i <= rest; i++) {
ways += process(i, rest - i);
}
return ways;
}
//动态规划
public static int ways2(int n) {
if (n < 1) {
return 0;
}
int[][] dp = new int[n + 1][n + 1]; //dp[i][j]的意思是当前pre为i,rest为j时有多少种分裂方法
//由于pre最小为1,所以第一行是没用的。
//当rest=0时,方法数为1,故dp数组的第一列除了第一行都为1
for (int i = 1; i <= n; i++) {
dp[i][0] = 1;
}
//若pre<rest时,方法数为0,故数组的左下半部分都是0
//按照从下往上,从左往右的顺序填表格
for (int pre = n ; pre > 0; pre--) {
for (int rest = pre ; rest < n + 1; rest++) {
for (int j = pre; j <= rest; j++) {
dp[pre][rest] += dp[j][rest - j];
}
}
}
return dp[1][n];
}
//斜率优化
public static int ways3(int n) {
if (n < 1) {
return 0;
}
int[][] dp = new int[n + 1][n + 1]; //dp[i][j]的意思是当前pre为i,rest为j时有多少种分裂方法
//由于pre最小为1,所以第一行是没用的。
//当rest=0时,方法数为1,故dp数组的第一列除了第一行都为1
for (int i = 1; i < n; i++) {
dp[i][0] = 1;
}
//若pre<rest时,方法数为0,故数组的左下半部分都是0
//按照从下往上,从左往右的顺序填表格
for (int i = 1; i <= n; i++) {
dp[i][i] = 1;
}
for (int pre = n - 1; pre > 0; pre--) {
for (int rest = pre + 1; rest <= n; rest++) {
//dp[pre + 1][rest]已经计算过了>=pre时的所有方法数,故dp[pre + 1][rest]=dp[pre+1][rest-pre-1]+dp[pre+2][rest-pre-2]+...+dp[rest][0],可以用它来代替for循环
dp[pre][rest] = dp[pre][rest - pre] + dp[pre + 1][rest];
}
}
return dp[1][n];
}
public static void main(String[] args) {
int n = 20;
System.out.println(ways1(n));
System.out.println(ways2(n));
System.out.println(ways3(n));
}
}
题目二十一
import java.util.HashMap;
import java.util.Map;
public class BiggestBSTTopologyInTree {
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int value) {
this.value = value;
}
}
public static int bstTopoSize1(Node head) {
if (head == null) {
return 0;
}
int max = maxTopo(head, head);
max = Math.max(bstTopoSize1(head.left), max);
max = Math.max(bstTopoSize1(head.right), max);
return max;
}
public static int maxTopo(Node h, Node n) {
if (h != null && n != null && isBSTNode(h, n, n.value)) {
return maxTopo(h, n.left) + maxTopo(h, n.right) + 1;
}
return 0;
}
public static boolean isBSTNode(Node h, Node n, int value) {
if (h == null) {
return false;
}
if (h == n) {
return true;
}
return isBSTNode(h.value > value ? h.left : h.right, n, value);
}
public static class Record {
public int l;
public int r;
public Record(int left, int right) {
this.l = left;
this.r = right;
}
}
public static int bstTopoSize2(Node head) {
Map<Node, Record> map = new HashMap<Node, Record>();
return posOrder(head, map);
}
public static int posOrder(Node h, Map<Node, Record> map) {
if (h == null) {
return 0;
}
int ls = posOrder(h.left, map);
int rs = posOrder(h.right, map);
modifyMap(h.left, h.value, map, true);
modifyMap(h.right, h.value, map, false);
Record lr = map.get(h.left);
Record rr = map.get(h.right);
int lbst = lr == null ? 0 : lr.l + lr.r + 1;
int rbst = rr == null ? 0 : rr.l + rr.r + 1;
map.put(h, new Record(lbst, rbst));
return Math.max(lbst + rbst + 1, Math.max(ls, rs));
}
public static int modifyMap(Node n, int v, Map<Node, Record> m, boolean s) {
if (n == null || (!m.containsKey(n))) {
return 0;
}
Record r = m.get(n);
if ((s && n.value > v) || ((!s) && n.value < v)) {
m.remove(n);
return r.l + r.r + 1;
} else {
int minus = modifyMap(s ? n.right : n.left, v, m, s);
if (s) {
r.r = r.r - minus;
} else {
r.l = r.l - minus;
}
m.put(n, r);
return minus;
}
}
// for test -- print tree
public static void printTree(Node head) {
System.out.println("Binary Tree:");
printInOrder(head, 0, "H", 17);
System.out.println();
}
public static void printInOrder(Node head, int height, String to, int len) {
if (head == null) {
return;
}
printInOrder(head.right, height + 1, "v", len);
String val = to + head.value + to;
int lenM = val.length();
int lenL = (len - lenM) / 2;
int lenR = len - lenM - lenL;
val = getSpace(lenL) + val + getSpace(lenR);
System.out.println(getSpace(height * len) + val);
printInOrder(head.left, height + 1, "^", len);
}
public static String getSpace(int num) {
String space = " ";
StringBuffer buf = new StringBuffer("");
for (int i = 0; i < num; i++) {
buf.append(space);
}
return buf.toString();
}
public static void main(String[] args) {
Node head = new Node(6);
head.left = new Node(1);
head.left.left = new Node(0);
head.left.right = new Node(3);
head.right = new Node(12);
head.right.left = new Node(10);
head.right.left.left = new Node(4);
head.right.left.left.left = new Node(2);
head.right.left.left.right = new Node(5);
head.right.left.right = new Node(14);
head.right.left.right.left = new Node(11);
head.right.left.right.right = new Node(15);
head.right.right = new Node(13);
head.right.right.left = new Node(20);
head.right.right.right = new Node(16);
printTree(head);
System.out.println(bstTopoSize1(head));
System.out.println(bstTopoSize2(head));
}
}
题目二十二
import java.util.Arrays;
public class ShuffleProblem {
// 数组的长度为len,调整前的位置是i,返回调整之后的位置
// 下标不从0开始,从1开始
public static int modifyIndex1(int i, int len) {
if (i <= len / 2) {
return 2 * i;
} else {
return 2 * (i - (len / 2)) - 1;
}
}
// 数组的长度为len,调整前的位置是i,返回调整之后的位置
// 下标不从0开始,从1开始
public static int modifyIndex2(int i, int len) {
return (2 * i) % (len + 1);
}
// 主函数
// 数组必须不为空,且长度为偶数
public static void shuffle(int[] arr) {
if (arr != null && arr.length != 0 && (arr.length & 1) == 0) {
shuffle(arr, 0, arr.length - 1);
}
}
// 在arr[L..R]上做完美洗牌的调整
public static void shuffle(int[] arr, int L, int R) {
while (R - L + 1 > 0) { // 切成一块一块的解决,每一块的长度满足(3^k)-1
int len = R - L + 1;
int base = 3;
int k = 1;
// 计算小于等于len并且是离len最近的,满足(3^k)-1的数
// 也就是找到最大的k,满足3^k <= len+1
while (base <= (len + 1) / 3) {
base *= 3;
k++;
}
// 当前要解决长度为base-1的块,一半就是再除2
int half = (base - 1) / 2;
// [L..R]的中点位置
int mid = (L + R) / 2;
// 要旋转的左部分为[L+half...mid], 右部分为arr[mid+1..mid+half]
// 注意在这里,arr下标是从0开始的
rotate(arr, L + half, mid, mid + half);
// 旋转完成后,从L开始算起,长度为base-1的部分进行下标连续推
cycles(arr, L, base - 1, k);
// 解决了前base-1的部分,剩下的部分继续处理
L = L + base - 1;
}
}
// 从start位置开始,往右len的长度这一段,做下标连续推
// 出发位置依次为1,3,9...
public static void cycles(int[] arr, int start, int len, int k) {
// 找到每一个出发位置trigger,一共k个
// 每一个trigger都进行下标连续推
// 出发位置是从1开始算的,而数组下标是从0开始算的。
for (int i = 0, trigger = 1; i < k; i++, trigger *= 3) {
int preValue = arr[trigger + start - 1];
int cur = modifyIndex2(trigger, len);
while (cur != trigger) {
int tmp = arr[cur + start - 1];
arr[cur + start - 1] = preValue;
preValue = tmp;
cur = modifyIndex2(cur, len);
}
arr[cur + start - 1] = preValue;
}
}
// [L..M]为左部分,[M+1..R]为右部分,左右两部分互换
public static void rotate(int[] arr, int L, int M, int R) {
reverse(arr, L, M);
reverse(arr, M + 1, R);
reverse(arr, L, R);
}
// [L..R]做逆序调整
public static void reverse(int[] arr, int L, int R) {
while (L < R) {
int tmp = arr[L];
arr[L++] = arr[R];
arr[R--] = tmp;
}
}
public static void wiggleSort(int[] arr) {
if (arr == null || arr.length == 0) {
return;
}
// 假设这个排序是额外空间复杂度O(1)的,当然系统提供的排序并不是,你可以自己实现一个堆排序
Arrays.sort(arr);
if ((arr.length & 1) == 1) {
shuffle(arr, 1, arr.length - 1);
} else {
shuffle(arr, 0, arr.length - 1);
for (int i = 0; i < arr.length; i += 2) {
int tmp = arr[i];
arr[i] = arr[i + 1];
arr[i + 1] = tmp;
}
}
}
// for test
public static boolean isValidWiggle(int[] arr) {
for (int i = 1; i < arr.length; i++) {
if ((i & 1) == 1 && arr[i] < arr[i - 1]) {
return false;
}
if ((i & 1) == 0 && arr[i] > arr[i - 1]) {
return false;
}
}
return true;
}
// for test
public static void printArray(int[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static int[] generateArray() {
int len = (int) (Math.random() * 10) * 2;
int[] arr = new int[len];
for (int i = 0; i < len; i++) {
arr[i] = (int) (Math.random() * 100);
}
return arr;
}
public static void main(String[] args) {
for (int i = 0; i < 5000000; i++) {
int[] arr = generateArray();
wiggleSort(arr);
if (!isValidWiggle(arr)) {
System.out.println("ooops!");
printArray(arr);
break;
}
}
}
}
题目二十三
public static boolean isValid(char[] s, char[] e) {
for (int i = 0; i < s.length; i++) {
if (s[i] == '*' || s[i] == '.') {
return false;
}
}
for (int i = 0; i < e.length; i++) {
if (e[i] == '*' && (i == 0 || e[i - 1] == '*')) {
return false;
}
}
return true;
}
public static boolean isMatch(String str, String exp) {
if (str == null || exp == null) {
return false;
}
char[] s = str.toCharArray();
char[] e = exp.toCharArray();
return isValid(s, e) ? process(s, e, 0, 0) : false;
}
public static boolean process(char[] s, char[] e, int si, int ei) {
if (ei == e.length) {
return si == s.length;
}
if (ei + 1 == e.length || e[ei + 1] != '*') {
return si != s.length && (e[ei] == s[si] || e[ei] == '.') && process(s, e, si + 1, ei + 1);
}
while (si != s.length && (e[ei] == s[si] || e[ei] == '.')) {
if (process(s, e, si, ei + 2)) {
return true;
}
si++;
}
return process(s, e, si, ei + 2);
}
public static boolean isMatchDP(String str, String exp) {
if (str == null || exp == null) {
return false;
}
char[] s = str.toCharArray();
char[] e = exp.toCharArray();
if (!isValid(s, e)) {
return false;
}
boolean[][] dp = initDPMap(s, e);
for (int i = s.length - 1; i > -1; i--) {
for (int j = e.length - 2; j > -1; j--) {
if (e[j + 1] != '*') {
dp[i][j] = (s[i] == e[j] || e[j] == '.') && dp[i + 1][j + 1];
} else {
int si = i;
while (si != s.length && (s[si] == e[j] || e[j] == '.')) {
if (dp[si][j + 2]) {
dp[i][j] = true;
break;
}
si++;
}
if (dp[i][j] != true) {
dp[i][j] = dp[si][j + 2];
}
}
}
}
return dp[0][0];
}
public static boolean[][] initDPMap(char[] s, char[] e) {
int slen = s.length;
int elen = e.length;
boolean[][] dp = new boolean[slen + 1][elen + 1];
dp[slen][elen] = true;
for (int j = elen - 2; j > -1; j = j - 2) {
if (e[j] != '*' && e[j + 1] == '*') {
dp[slen][j] = true;
} else {
break;
}
}
if (slen > 0 && elen > 0) {
if ((e[elen - 1] == '.' || s[slen - 1] == e[elen - 1])) {
dp[slen - 1][elen - 1] = true;
}
}
return dp;
}
public static void main(String[] args) {
String str = "abcccdefg";
String exp = "ab.*d.*e.*";
System.out.println(isMatch(str, exp));
System.out.println(isMatchDP(str, exp));
}
题目二十四
public static class Node {
public Node[] nexts = new Node[2];
}
public static class NumTrie {
public Node head = new Node();
public void add(int num) {
Node cur = head;
for (int move = 31; move >= 0; move--) {
int path = ((num >> move) & 1);
cur.nexts[path] = cur.nexts[path] == null ? new Node() : cur.nexts[path];
cur = cur.nexts[path];
}
}
public int maxXor(int num) {
Node cur = head;
int res = 0;
for (int move = 31; move >= 0; move--) {
int path = (num >> move) & 1;
int best = move == 31 ? path : (path ^ 1);
best = cur.nexts[best] != null ? best : (best ^ 1);
res |= (path ^ best) << move;
cur = cur.nexts[best];
}
return res;
}
}
public static int maxXorSubarray(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int max = Integer.MIN_VALUE;
int eor = 0;
NumTrie numTrie = new NumTrie();
numTrie.add(0);
for (int i = 0; i < arr.length; i++) {
eor ^= arr[i];
max = Math.max(max, numTrie.maxXor(eor));
numTrie.add(eor);
}
return max;
}
// for test
public static int comparator(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
int max = Integer.MIN_VALUE;
for (int i = 0; i < arr.length; i++) {
int eor = 0;
for (int j = i; j < arr.length; j++) {
eor ^= arr[j];
max = Math.max(max, eor);
}
}
return max;
}
// for test
public static int[] generateRandomArray(int maxSize, int maxValue) {
int[] arr = new int[(int) ((maxSize + 1) * Math.random())];
for (int i = 0; i < arr.length; i++) {
arr[i] = (int) ((maxValue + 1) * Math.random()) - (int) (maxValue * Math.random());
}
return arr;
}
// for test
public static void printArray(int[] arr) {
if (arr == null) {
return;
}
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static void main(String[] args) {
int testTime = 500000;
int maxSize = 30;
int maxValue = 50;
boolean succeed = true;
for (int i = 0; i < testTime; i++) {
int[] arr = generateRandomArray(maxSize, maxValue);
int res = maxXorSubarray(arr);
int comp = comparator(arr);
if (res != comp) {
succeed = false;
printArray(arr);
System.out.println(res);
System.out.println(comp);
break;
}
}
System.out.println(succeed ? "Nice!" : "Fucking fucked!");
}
题目二十五
public static int maxCoins1(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0];
}
int N = arr.length;
int[] help = new int[N + 2];
help[0] = 1;
help[N + 1] = 1;
for (int i = 0; i < N; i++) {
help[i + 1] = arr[i];
}
return process(help, 1, N);
}
// 打爆arr[L..R]范围上的所有气球,返回最大的分数
// 假设arr[L-1]和arr[R+1]一定没有被打爆
public static int process(int[] arr, int L, int R) {
if (L == R) {// 如果arr[L..R]范围上只有一个气球,直接打爆即可
return arr[L - 1] * arr[L] * arr[R + 1];
}
// 最后打爆arr[L]的方案,和最后打爆arr[R]的方案,先比较一下
int max = Math.max(
arr[L - 1] * arr[L] * arr[R + 1] + process(arr, L + 1, R),
arr[L - 1] * arr[R] * arr[R + 1] + process(arr, L, R - 1));
// 尝试中间位置的气球最后被打爆的每一种方案
for (int i = L + 1; i < R; i++) {
max = Math.max(max,
arr[L - 1] * arr[i] * arr[R + 1] + process(arr, L, i - 1)
+ process(arr, i + 1, R));
}
return max;
}
public static int maxCoins2(int[] arr) {
if (arr == null || arr.length == 0) {
return 0;
}
if (arr.length == 1) {
return arr[0];
}
int N = arr.length;
int[] help = new int[N + 2];
help[0] = 1;
help[N + 1] = 1;
for (int i = 0; i < N; i++) {
help[i + 1] = arr[i];
}
int[][] dp = new int[N + 2][N + 2];
for (int i = 1; i <= N; i++) {
dp[i][i] = help[i - 1] * help[i] * help[i + 1];
System.out.println(dp[i][i]);
}
for (int L = N; L >= 1; L--) {
for (int R = L + 1; R <= N; R++) {
// 求解dp[L][R],表示help[L..R]上打爆所有气球的最大分数
// 最后打爆help[L]的方案
int finalL = help[L - 1] * help[L] * help[R + 1] + dp[L + 1][R];
// 最后打爆help[R]的方案
int finalR = help[L - 1] * help[R] * help[R + 1] + dp[L][R - 1];
// 最后打爆help[L]的方案,和最后打爆help[R]的方案,先比较一下
dp[L][R] = Math.max(finalL, finalR);
// 尝试中间位置的气球最后被打爆的每一种方案
for (int i = L + 1; i < R; i++) {
dp[L][R] = Math.max(dp[L][R], help[L - 1] * help[i]
* help[R + 1] + dp[L][i - 1] + dp[i + 1][R]);
}
}
}
return dp[1][N];
}
public static void main(String[] args) {
int[] arr = { 4, 2, 3, 5, 1, 6 };
System.out.println(maxCoins1(arr));
System.out.println(maxCoins2(arr));
}
题目二十六
public static int step1(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
return process(arr, arr.length - 1, 1, 2, 3);
}
public static int process(int[] arr, int i, int from, int mid, int to) {
if (i == -1) {
return 0;
}
if (arr[i] != from && arr[i] != to) {
return -1;
}
if (arr[i] == from) {
return process(arr, i - 1, from, to, mid);
} else {
int rest = process(arr, i - 1, mid, from, to);
if (rest == -1) {
return -1;
}
return (1 << i) + rest;
}
}
public static int step2(int[] arr) {
if (arr == null || arr.length == 0) {
return -1;
}
int from = 1;
int mid = 2;
int to = 3;
int i = arr.length - 1;
int res = 0;
int tmp = 0;
while (i >= 0) {
if (arr[i] != from && arr[i] != to) {
return -1;
}
if (arr[i] == to) {
res += 1 << i;
tmp = from;
from = mid;
} else {
tmp = to;
to = mid;
}
mid = tmp;
i--;
}
return res;
}
public static void main(String[] args) {
int[] arr = { 3, 3, 2, 1 };
System.out.println(step1(arr));
System.out.println(step2(arr));
}
题目二十七
public static boolean sameTypeSameNumber(char[] str1, char[] str2) {
if (str1.length != str2.length) {
return false;
}
int[] map = new int[256];
for (int i = 0; i < str1.length; i++) {
map[str1[i]]++;
}
for (int i = 0; i < str2.length; i++) {
if (--map[str2[i]] < 0) {
return false;
}
}
return true;
}
public static boolean isScramble1(String s1, String s2) {
if ((s1 == null && s2 != null) || (s1 != null && s2 == null)) {
return false;
}
if (s1 == null && s2 == null) {
return true;
}
if (s1.equals(s2)) {
return true;
}
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
if (!sameTypeSameNumber(str1, str2)) {
return false;
}
int N = s1.length();
return process(str1, str2, 0, 0, N);
}
// 返回str1[从L1开始往右长度为size的子串]和str2[从L2开始往右长度为size的子串]是否互为旋变字符串
// 在str1中的这一段和str2中的这一段一定是等长的,所以只用一个参数size
public static boolean process(char[] str1, char[] str2, int L1, int L2,
int size) {
if (size == 1) {
return str1[L1] == str2[L2];
}
// 枚举每一种情况,有一个计算出互为旋变就返回true。都算不出来最后返回false
for (int leftPart = 1; leftPart < size; leftPart++) {
if ((process(str1, str2, L1, L2, leftPart) && process(str1, str2,
L1 + leftPart, L2 + leftPart, size - leftPart))
|| (process(str1, str2, L1, L2 + size - leftPart, leftPart) && process(
str1, str2, L1 + leftPart, L2, size - leftPart))) {
return true;
}
}
return false;
}
public static boolean isScramble2(String s1, String s2) {
if ((s1 == null && s2 != null) || (s1 != null && s2 == null)) {
return false;
}
if (s1 == null && s2 == null) {
return true;
}
if (s1.equals(s2)) {
return true;
}
char[] str1 = s1.toCharArray();
char[] str2 = s2.toCharArray();
if (!sameTypeSameNumber(str1, str2)) {
return false;
}
int N = s1.length();
boolean[][][] dp = new boolean[N][N][N + 1];
for (int L1 = 0; L1 < N; L1++) {
for (int L2 = 0; L2 < N; L2++) {
dp[L1][L2][1] = str1[L1] == str2[L2];
}
}
// 第一层for循环含义是:依次填size=2层、size=3层..size=N层,每一层都是一个二维平面
// 第二、三层for循环含义是:在具体的一层,整个面都要填写,所以用两个for循环去填一个二维面
// L1的取值氛围是[0,N-size],因为从L1出发往右长度为size的子串,L1是不能从N-size+1出发的,这样往右就不够size个字符了
// L2的取值范围同理
// 第4层for循环完全是递归函数怎么写,这里就怎么改的
for (int size = 2; size <= N; size++) {
for (int L1 = 0; L1 <= N - size; L1++) {
for (int L2 = 0; L2 <= N - size; L2++) {
for (int leftPart = 1; leftPart < size; leftPart++) {
if ((dp[L1][L2][leftPart] && dp[L1 + leftPart][L2
+ leftPart][size - leftPart])
|| (dp[L1][L2 + size - leftPart][leftPart] && dp[L1
+ leftPart][L2][size - leftPart])) {
dp[L1][L2][size] = true;
break;
}
}
}
}
}
return dp[0][0][N];
}
public static void main(String[] args) {
String test1 = "abcd";
String test2 = "cdab";
System.out.println(isScramble1(test1, test2));
System.out.println(isScramble2(test1, test2));
test1 = "abcd";
test2 = "cadb";
System.out.println(isScramble1(test1, test2));
System.out.println(isScramble2(test1, test2));
test1 = "bcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcdebcde";
test2 = "ebcdeebcdebebcdebcdebcdecdebcbcdcdebcddebcbdebbbcdcdebcdeebcdebcdeebcddeebccdebcdbcdebcd";
// System.out.println(isScramble1(test1, test2));
System.out.println(isScramble2(test1, test2));
}
题目二十八
public static int minLength(String str1, String str2) {
if (str1 == null || str2 == null || str1.length() < str2.length()) {
return 0;
}
char[] chas1 = str1.toCharArray();
char[] chas2 = str2.toCharArray();
int[] map = new int[256];
for (int i = 0; i != chas2.length; i++) {
map[chas2[i]]++;
}
int left = 0;
int right = 0;
int match = chas2.length;
int minLen = Integer.MAX_VALUE;
while (right != chas1.length) {
map[chas1[right]]--;
if (map[chas1[right]] >= 0) {
match--;
}
if (match == 0) {
while (map[chas1[left]] < 0) {
map[chas1[left++]]++;
}
minLen = Math.min(minLen, right - left + 1);
match++;
map[chas1[left++]]++;
}
right++;
}
return minLen == Integer.MAX_VALUE ? 0 : minLen;
}
public static void main(String[] args) {
String str1 = "adabbca";
String str2 = "acb";
System.out.println(minLength(str1, str2));
}
题目二十九
// 节点的数据结构
public static class Node {
public Integer key;
public Integer value;
public Integer times; // 这个节点发生get或者set的次数总和
public Node up; // 节点之间是双向链表所以有上一个节点
public Node down;// 节点之间是双向链表所以有下一个节点
public Node(int key, int value, int times) {
this.key = key;
this.value = value;
this.times = times;
}
}
// 桶结构
public static class NodeList {
public Node head; // 桶的头节点
public Node tail; // 桶的尾节点
public NodeList last; // 桶之间是双向链表所以有前一个桶
public NodeList next; // 桶之间是双向链表所以有后一个桶
public NodeList(Node node) {
head = node;
tail = node;
}
// 把一个新的节点加入这个桶,新的节点都放在顶端变成新的头部
public void addNodeFromHead(Node newHead) {
newHead.down = head;
head.up = newHead;
head = newHead;
}
// 判断这个桶是不是空的
public boolean isEmpty() {
return head == null;
}
// 删除node节点并保证node的上下环境重新连接
public void deleteNode(Node node) {
if (head == tail) {
head = null;
tail = null;
} else {
if (node == head) {
head = node.down;
head.up = null;
} else if (node == tail) {
tail = node.up;
tail.down = null;
} else {
node.up.down = node.down;
node.down.up = node.up;
}
}
node.up = null;
node.down = null;
}
}
// 总的缓存结构
public static class LFUCache {
private int capacity; // 缓存的大小限制,即K
private int size; // 缓存目前有多少个节点
private HashMap<Integer, Node> records;// 表示key(Integer)由哪个节点(Node)代表
private HashMap<Node, NodeList> heads; // 表示节点(Node)在哪个桶(NodeList)里
private NodeList headList; // 整个结构中位于最左的桶
public LFUCache(int K) {
this.capacity = K;
this.size = 0;
this.records = new HashMap<>();
this.heads = new HashMap<>();
headList = null;
}
// removeNodeList:刚刚减少了一个节点的桶
// 这个函数的功能是,判断刚刚减少了一个节点的桶是不是已经空了。
// 1)如果不空,什么也不做
//
// 2)如果空了,removeNodeList还是整个缓存结构最左的桶(headList)。
// 删掉这个桶的同时也要让最左的桶变成removeNodeList的下一个。
//
// 3)如果空了,removeNodeList不是整个缓存结构最左的桶(headList)。
// 把这个桶删除,并保证上一个的桶和下一个桶之间还是双向链表的连接方式
//
// 函数的返回值表示刚刚减少了一个节点的桶是不是已经空了,空了返回true;不空返回false
private boolean modifyHeadList(NodeList removeNodeList) {
if (removeNodeList.isEmpty()) {
if (headList == removeNodeList) {
headList = removeNodeList.next;
if (headList != null) {
headList.last = null;
}
} else {
removeNodeList.last.next = removeNodeList.next;
if (removeNodeList.next != null) {
removeNodeList.next.last = removeNodeList.last;
}
}
return true;
}
return false;
}
// 函数的功能
// node这个节点的次数+1了,这个节点原来在oldNodeList里。
// 把node从oldNodeList删掉,然后放到次数+1的桶中
// 整个过程既要保证桶之间仍然是双向链表,也要保证节点之间仍然是双向链表
private void move(Node node, NodeList oldNodeList) {
oldNodeList.deleteNode(node);
// preList表示次数+1的桶的前一个桶是谁
// 如果oldNodeList删掉node之后还有节点,oldNodeList就是次数+1的桶的前一个桶
// 如果oldNodeList删掉node之后空了,oldNodeList是需要删除的,所以次数+1的桶的前一个桶,是oldNodeList的前一个
NodeList preList = modifyHeadList(oldNodeList) ? oldNodeList.last
: oldNodeList;
// nextList表示次数+1的桶的后一个桶是谁
NodeList nextList = oldNodeList.next;
if (nextList == null) {
NodeList newList = new NodeList(node);
if (preList != null) {
preList.next = newList;
}
newList.last = preList;
if (headList == null) {
headList = newList;
}
heads.put(node, newList);
} else {
if (nextList.head.times.equals(node.times)) {
nextList.addNodeFromHead(node);
heads.put(node, nextList);
} else {
NodeList newList = new NodeList(node);
if (preList != null) {
preList.next = newList;
}
newList.last = preList;
newList.next = nextList;
nextList.last = newList;
if (headList == nextList) {
headList = newList;
}
heads.put(node, newList);
}
}
}
public void set(int key, int value) {
if (records.containsKey(key)) {
Node node = records.get(key);
node.value = value;
node.times++;
NodeList curNodeList = heads.get(node);
move(node, curNodeList);
} else {
if (size == capacity) {
Node node = headList.tail;
headList.deleteNode(node);
modifyHeadList(headList);
records.remove(node.key);
heads.remove(node);
size--;
}
Node node = new Node(key, value, 1);
if (headList == null) {
headList = new NodeList(node);
} else {
if (headList.head.times.equals(node.times)) {
headList.addNodeFromHead(node);
} else {
NodeList newList = new NodeList(node);
newList.next = headList;
headList.last = newList;
headList = newList;
}
}
records.put(key, node);
heads.put(node, headList);
size++;
}
}
public Integer get(int key) {
if (!records.containsKey(key)) {
return null;
}
Node node = records.get(key);
node.times++;
NodeList curNodeList = heads.get(node);
move(node, curNodeList);
return node.value;
}
}
题目三十
public static boolean[] stations(int[] dis, int[] oil) {
if (dis == null || oil == null || dis.length < 2
|| dis.length != oil.length) {
return null;
}
int init = changeDisArrayGetInit(dis, oil);
return init == -1 ? new boolean[dis.length] : enlargeArea(dis, init);
}
public static int changeDisArrayGetInit(int[] dis, int[] oil) {
int init = -1;
for (int i = 0; i < dis.length; i++) {
dis[i] = oil[i] - dis[i];
if (dis[i] >= 0) {
init = i;
}
}
return init;
}
public static boolean[] enlargeArea(int[] dis, int init) {
boolean[] res = new boolean[dis.length];
int start = init;
int end = nextIndex(init, dis.length);
int need = 0;
int rest = 0;
do {
// 当前来到的start已经在连通区域中,可以确定后续的开始点一定无法转完一圈
if (start != init && start == lastIndex(end, dis.length)) {
break;
}
// 当前来到的start不在连通区域中,就扩充连通区域
if (dis[start] < need) { // 从当前start出发,无法到达initial点
need -= dis[start];
} else { // 如start可以到达initial点,扩充连通区域的结束点
rest += dis[start] - need;
need = 0;
while (rest >= 0 && end != start) {
rest += dis[end];
end = nextIndex(end, dis.length);
}
// 如果连通区域已经覆盖整个环,当前的start是良好出发点,进入2阶段
if (rest >= 0) {
res[start] = true;
connectGood(dis, lastIndex(start, dis.length), init, res);
break;
}
}
start = lastIndex(start, dis.length);
} while (start != init);
return res;
}
// 已知start的next方向上有一个良好出发点
// start如果可以达到这个良好出发点,那么从start出发一定可以转一圈
public static void connectGood(int[] dis, int start, int init, boolean[] res) {
int need = 0;
while (start != init) {
if (dis[start] < need) {
need -= dis[start];
} else {
res[start] = true;
need = 0;
}
start = lastIndex(start, dis.length);
}
}
public static int lastIndex(int index, int size) {
return index == 0 ? (size - 1) : index - 1;
}
public static int nextIndex(int index, int size) {
return index == size - 1 ? 0 : (index + 1);
}
// for test
public static boolean[] test(int[] dis, int[] oil) {
if (dis == null || oil == null || dis.length < 2
|| dis.length != oil.length) {
return null;
}
boolean[] res = new boolean[dis.length];
for (int i = 0; i < dis.length; i++) {
dis[i] = oil[i] - dis[i];
}
for (int i = 0; i < dis.length; i++) {
res[i] = canWalkThrough(dis, i);
}
return res;
}
// for test
public static boolean canWalkThrough(int[] arr, int index) {
int sum = 0;
for (int i = 0; i < arr.length; i++) {
sum += arr[index];
if (sum < 0) {
return false;
}
index = nextIndex(index, arr.length);
}
return true;
}
// for test
public static void printArray(int[] dis, int[] oil) {
for (int i = 0; i < dis.length; i++) {
System.out.print(oil[i] - dis[i] + " ");
}
System.out.println();
}
// for test
public static void printBooleanArray(boolean[] arr) {
for (int i = 0; i < arr.length; i++) {
System.out.print(arr[i] + " ");
}
System.out.println();
}
// for test
public static int[] generateArray(int size, int max) {
int[] res = new int[size];
for (int i = 0; i < res.length; i++) {
res[i] = (int) (Math.random() * max);
}
return res;
}
// for test
public static int[] copyArray(int[] arr) {
int[] res = new int[arr.length];
for (int i = 0; i < res.length; i++) {
res[i] = arr[i];
}
return res;
}
// for test
public static boolean isEqual(boolean[] res1, boolean[] res2) {
for (int i = 0; i < res1.length; i++) {
if (res1[i] != res2[i]) {
return false;
}
}
return true;
}
public static void main(String[] args) {
int max = 20;
for (int i = 0; i < 5000000; i++) {
int size = (int) (Math.random() * 20) + 2;
int[] dis = generateArray(size, max);
int[] oil = generateArray(size, max);
int[] dis1 = copyArray(dis);
int[] oil1 = copyArray(oil);
int[] dis2 = copyArray(dis);
int[] oil2 = copyArray(oil);
boolean[] res1 = stations(dis1, oil1);
boolean[] res2 = test(dis2, oil2);
if (!isEqual(res1, res2)) {
printArray(dis, oil);
printBooleanArray(res1);
printBooleanArray(res2);
System.out.println("what a fucking day!");
break;
}
}
}
题目三十一
public static class Node {
public int value;
public Node left;
public Node right;
public Node(int data) {
this.value = data;
}
}
public static Node[] getTwoErrNodes(Node head) {
Node[] errs = new Node[2];
if (head == null) {
return errs;
}
Stack<Node> stack = new Stack<Node>();
Node pre = null;
while (!stack.isEmpty() || head != null) {
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
if (pre != null && pre.value > head.value) {
errs[0] = errs[0] == null ? pre : errs[0];
errs[1] = head;
}
pre = head;
head = head.right;
}
}
return errs;
}
public static Node[] getTwoErrParents(Node head, Node e1, Node e2) {
Node[] parents = new Node[2];
if (head == null) {
return parents;
}
Stack<Node> stack = new Stack<Node>();
while (!stack.isEmpty() || head != null) {
if (head != null) {
stack.push(head);
head = head.left;
} else {
head = stack.pop();
if (head.left == e1 || head.right == e1) {
parents[0] = head;
}
if (head.left == e2 || head.right == e2) {
parents[1] = head;
}
head = head.right;
}
}
return parents;
}
public static Node recoverTree(Node head) {
Node[] errs = getTwoErrNodes(head);
Node[] parents = getTwoErrParents(head, errs[0], errs[1]);
Node e1 = errs[0];
Node e1P = parents[0];
Node e1L = e1.left;
Node e1R = e1.right;
Node e2 = errs[1];
Node e2P = parents[1];
Node e2L = e2.left;
Node e2R = e2.right;
if (e1 == head) {
if (e1 == e2P) {
e1.left = e2L;
e1.right = e2R;
e2.right = e1;
e2.left = e1L;
} else if (e2P.left == e2) {
e2P.left = e1;
e2.left = e1L;
e2.right = e1R;
e1.left = e2L;
e1.right = e2R;
} else {
e2P.right = e1;
e2.left = e1L;
e2.right = e1R;
e1.left = e2L;
e1.right = e2R;
}
head = e2;
} else if (e2 == head) {
if (e2 == e1P) {
e2.left = e1L;
e2.right = e1R;
e1.left = e2;
e1.right = e2R;
} else if (e1P.left == e1) {
e1P.left = e2;
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1R;
} else {
e1P.right = e2;
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1R;
}
head = e1;
} else {
if (e1 == e2P) {
if (e1P.left == e1) {
e1P.left = e2;
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1;
} else {
e1P.right = e2;
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1;
}
} else if (e2 == e1P) {
if (e2P.left == e2) {
e2P.left = e1;
e2.left = e1L;
e2.right = e1R;
e1.left = e2;
e1.right = e2R;
} else {
e2P.right = e1;
e2.left = e1L;
e2.right = e1R;
e1.left = e2;
e1.right = e2R;
}
} else {
if (e1P.left == e1) {
if (e2P.left == e2) {
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1R;
e1P.left = e2;
e2P.left = e1;
} else {
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1R;
e1P.left = e2;
e2P.right = e1;
}
} else {
if (e2P.left == e2) {
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1R;
e1P.right = e2;
e2P.left = e1;
} else {
e1.left = e2L;
e1.right = e2R;
e2.left = e1L;
e2.right = e1R;
e1P.right = e2;
e2P.right = e1;
}
}
}
}
return head;
}
题目三十二
public static class Rectangle {
public int up;
public int down;
public int left;
public int right;
public Rectangle(int up, int down, int left, int right) {
this.up = up;
this.down = down;
this.left = left;
this.right = right;
}
}
public static class DownComparator implements Comparator<Rectangle> {
@Override
public int compare(Rectangle o1, Rectangle o2) {
return o1.down - o2.down;
}
}
public static class LeftComparator implements Comparator<Rectangle> {
@Override
public int compare(Rectangle o1, Rectangle o2) {
return o1.left - o2.left;
}
}
public static class RightComparator implements Comparator<Rectangle> {
@Override
public int compare(Rectangle o1, Rectangle o2) {
return o1.right - o2.right;
}
}
public static int maxCover(Rectangle[] recs) {
if (recs == null || recs.length == 0) {
return 0;
}
Arrays.sort(recs, new DownComparator());
TreeSet<Rectangle> leftOrdered = new TreeSet<>(new LeftComparator());
int ans = 0;
for (int i = 0; i < recs.length; i++) {
int curDown = recs[i].down;
int index = i;
while (recs[index].down == curDown) {
leftOrdered.add(recs[index]);
index++;
}
i = index;
removeLowerOnCurDown(leftOrdered, curDown);
TreeSet<Rectangle> rightOrdered = new TreeSet<>(new RightComparator());
for (Rectangle rec : leftOrdered) {
removeLeftOnCurLeft(rightOrdered, rec.left);
rightOrdered.add(rec);
ans = Math.max(ans, rightOrdered.size());
}
}
return ans;
}
public static void removeLowerOnCurDown(TreeSet<Rectangle> set, int curDown) {
List<Rectangle> removes = new ArrayList<>();
for (Rectangle rec : set) {
if (rec.up <= curDown) {
removes.add(rec);
}
}
for (Rectangle rec : removes) {
set.remove(rec);
}
}
public static void removeLeftOnCurLeft(TreeSet<Rectangle> rightOrdered, int curLeft) {
List<Rectangle> removes = new ArrayList<>();
for (Rectangle rec : rightOrdered) {
if (rec.right > curLeft) {
break;
}
removes.add(rec);
}
for (Rectangle rec : removes) {
rightOrdered.remove(rec);
}
}