基础讲解

一、认识复杂度和简单排序算法

1.如何交换两个数据

public void swap(int[] arr, int i, int j){    // 当i和j是一个位置的时候会出错
    arr[i] = arr[i] ^ arr[j];
    arr[j] = arr[i] ^ arr[j];
    arr[i] = arr[i] ^ arr[j];
}

2.一堆数据中只有一个数出现的次数为奇数次,其余都为偶数次,如何求出这个出现奇数次的数

// 由于异或运算的性质,出现偶数次的数进行异或后的结果为零,所以全部数异或后的结果就是出现奇数次的那个数
public void printOddTimesNum(int[] arr){
    int eor = 0;
    for(int num : arr){
        eor ^= num;
    }
    System.out.println(eor);
}

3.一堆数据中有两个数出现的次数为奇数次,其余都为偶数次,如何求出这两个出现奇数次的数

做法:假设a和b是两个出现奇数次的数,则所有数据的异或结果eor就是ab,如果ab的结果eor不为0,则eor上至少有一位是1。那么a和b对应位置上一个是0一个是1,再将全部数据分为该位置为0和为1的两组数据,将该位置都为0或者都为1的数据进行异或,得到a或者b,再将得到的结果与eor异或得到另一个数。

public void printOddTimesNum(int[] arr){
    int eor = 0;
    for(int num : arr){
        eor ^= num;
    }
    //若eor!=0,则eor必然有一个位置上是1
    int rightOne = eor & (~eor + 1);    //提取出最右的1
    int onlyOne = 0;
    for(int num : arr){
        if((num & rightOne) == 0){    //将对应位置上是0的数全部进行异或,即可得到一个奇数次的数
            onlyOne ^= num;
        }
    }
    System.out.println(onlyOne + "," + (eor^onlyOne));
}

4.递归行为时间复杂度的估算

master公式的使用:T(N)=a*T(N/b)+O(N^d)
1)当log(b,a)>d -> 复杂度为O(N^log(b,a))
2)当log(b,a)=d -> 复杂度为O(N^(d*logN))
3)当log(b,a)<d -> 复杂度为O(N^d)

二、排序算法

1.选择排序

时间复杂度O(n^2),空间复杂度O(1),不稳定
思路:

  • 每一轮选择,找到最大(最小)的元素,并把它交换到合适的位置
//选择最小值
public void selectionSort(int[] arr){
    if(arr==null||arr.length<2){
        return;    
    }
    for(int i=0;i<arr.length-1;i++){
        int minIndex=i;
        for(int j=i+1;j<arr.length;j++){
            minIndex=arr[j]<arr[minIndex]?j:minIndex;
        }
        swap(arr,i,minIndex);
    }
}

public void swap(int[] arr,int i,int j){
    int tmp=arr[i];
    arr[i]=arr[j];
    arr[j]=tmp;
}
//选择最大值
public void selectionSort(int[] arr){
    if(arr==null||arr.length<2){
        return;    
    }
    for(int right=arr.length-1;right>0;right--){
        int max=right;
        for(int left=0;i<right;i++){
            max=arr[max]<arr[left]?left:max;        
        }
        if(max!=right){
            swap(arr,max,right);        
        }
    }
}

2.冒泡排序

时间复杂度O(n^2),空间复杂度O(1),稳定
思路:

  • 每轮冒泡不断地比较相邻的两个元素,如果他们是逆序的,则交换它们的位置
  • 下一轮冒泡,可以调整未排序的右边界,减少不必要的比较
public void bubbleSort(int[] arr){
    if(arr==null||arr.length<2){
        return;    
    }
    for(int i=arr.length-1;i>0;i--){
        for(int j=0;j<i;j++){
            if(arr[j]>arr[j+1]){
                swap(arr,j,j+1);            
            }
        }
    }
}
public void bubbleSort(int[] arr){
    if(arr==null||arr.length<2){
        return;    
    }
    int j=arr.length-1;
    while(true){
        int x=0;    //用于确定右边界
        for(int i=0;i<j;i++){
            if(arr[i]>arr[i+1]){
                swap(arr,i,i+1);
                x=i;            
            }        
        }
        j=x;
        if(j==0){
            break;        
        }
    }
}

3.插入排序

时间复杂度O(n^2),空间复杂度O(1),稳定
思路:

  • 将数组分为两部分,[0...low-1]已排序好区域,[low...arr.length-1]未排序好区域
  • 每次从未排序区域取出low位置的元素插入到已排序区域
public void insertionSort(int[] arr){
    if(arr==null||arr.length<2){
        return;    
    }
    for(int i=1;i<arr.length;i++){
        for(int j=i-1;j>=0&&arr[j]>arr[j+1];j--){
            swap(arr,j,j+1);        
        }    
    }
}
public void insertionSort(int[] arr){
    for(int low=1;low<arr.length;low++){
        //将low位置的元素插入至[0...low-1]已排序好区域
        int t=arr[low];
        int i=low-1;//已排序区域指针
        while(i>=0&&t<a[i]){
            arr[i+1]=arr[i];
            i--;        
        }    
        //找到插入位置
        if(i!=low-1){
            arr[i+1]=t;        
        }
    }
}

4.归并排序

时间复杂度O(N*logN),空间复杂度O(N),稳定
思路:

  • 分 - 每次从中间切一刀,处理的数据少一半
  • 治 - 当数据仅剩一个时可以认为有序
  • 合 - 两个有序的结果,可以进行合并排序
public static void mergeSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    mergeSort(arr, 0, arr.length - 1);
}

public static void mergeSort(int[] arr, int l, int r) {
    if (l == r) {
        return;
    }
    int mid = l + ((r - l) >> 1);
    mergeSort(arr, l, mid);
    mergeSort(arr, mid + 1, r);
    merge(arr, l, mid, r);
}

public static void merge(int[] arr, int l, int m, int r) {
    int[] help = new int[r - l + 1];
    int i = 0;
    int p1 = l;
    int p2 = m + 1;
    while (p1 <= m && p2 <= r) {
        help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2++];
    }
    while (p1 <= m) {
        help[i++] = arr[p1++];
    }
    while (p2 <= r) {
        help[i++] = arr[p2++];
    }
    for (i = 0; i < help.length; i++) {
        arr[l + i] = help[i];
    }
}
public class MergeSortTopDown {

    /*
        a1 原始数组
        i~iEnd 第一个有序范围
        j~jEnd 第二个有序范围
        a2 临时数组
     */
    public static void merge(int[] a1, int i, int iEnd, int j, int jEnd, int[] a2) {
        int k = i;
        while (i <= iEnd && j <= jEnd) {
            if (a1[i] < a1[j]) {
                a2[k] = a1[i];
                i++;
            } else {
                a2[k] = a1[j];
                j++;
            }
            k++;
        }
        if (i > iEnd) {
            System.arraycopy(a1, j, a2, k, jEnd - j + 1);
        }
        if (j > jEnd) {
            System.arraycopy(a1, i, a2, k, iEnd - i + 1);
        }
    }

    public static void sort(int[] a1) {
        int[] a2 = new int[a1.length];
        split(a1, 0, a1.length - 1, a2);
    }

    private static void split(int[] a1, int left, int right, int[] a2) {
        int[] array = Arrays.copyOfRange(a1, left, right + 1);
//        System.out.println(Arrays.toString(array));
        // 2. 治
        if (left == right) {
            return;
        }
        // 1. 分
        int m = (left + right) >>> 1;
        split(a1, left, m, a2);                 // left = 0 m = 0  9
        split(a1, m + 1, right, a2);       // m+1 = 1 right = 1  3
        // 3. 合
        merge(a1, left, m, m + 1, right, a2);
        System.arraycopy(a2, left, a1, left, right - left + 1);
    }

    public static void main(String[] args) {
        int[] a = {9, 3, 7, 2, 8, 5, 1, 4};
        System.out.println(Arrays.toString(a));
        sort(a);
        System.out.println(Arrays.toString(a));
    }
}
public class MergeSortBottomUp {

    /*
        a1 原始数组
        i~iEnd 第一个有序范围
        j~jEnd 第二个有序范围
        a2 临时数组
     */
    public static void merge(int[] a1, int i, int iEnd, int j, int jEnd, int[] a2) {
        int k = i;
        while (i <= iEnd && j <= jEnd) {
            if (a1[i] < a1[j]) {
                a2[k] = a1[i];
                i++;
            } else {
                a2[k] = a1[j];
                j++;
            }
            k++;
        }
        if (i > iEnd) {
            System.arraycopy(a1, j, a2, k, jEnd - j + 1);
        }
        if (j > jEnd) {
            System.arraycopy(a1, i, a2, k, iEnd - i + 1);
        }
    }

    public static void sort(int[] a1) {
        int n = a1.length;
        int[] a2 = new int[n];
        for (int width = 1; width < n; width *= 2) {
            for (int i = 0; i < n; i += 2 * width) {
                int m = Integer.min(i + width - 1, n - 1);
                int j = Integer.min(i + 2 * width - 1, n - 1);
                System.out.println(i + " " + m + " " + j);
                merge(a1, i, m, m + 1, j, a2);
            }
            System.arraycopy(a2, 0, a1, 0, n);
        }
    }

    public static void main(String[] args) {
        int[] a = {9, 3, 7, 2, 8, 5, 1, 4};
        System.out.println(Arrays.toString(a));
        sort(a);
        System.out.println(Arrays.toString(a));
    }
}

5.快速排序

快速排序的整体思路:
1.假设基准数字是x,每一次排序完达到的效果是小于等于x的数都在数组的左边,大于x的数都在数组的右边。
2.先划定一个小于等于的范围,初始右边界在-1处;划定一个大于的范围,初始左边界在arr.length处。
3.从左往右开始遍历每一个数,如果arr[i]<=x,就让arr[i]跟小于等于的右边界的下一个数进行交换,右边界向右移一位,i++;如果arr[i]>x,就让arr[i]跟大于的左边界的前一个数进行交换,左边界向左移一位,i不动。
4.等所有的数都遍历完了,小于等于x的就都在左边,大于x的都在右边。

快排1.0,选择的基准数字为数组最右的那个数,将数组分为两个部分,≤x、>x。
快排2.0,选择的基准数字为数组最右的那个数,将数组分为三个部分,<x、=x、>x。
快排3.0,选择的基准数字是数组中随机选取的一个数字,将选取的数跟数组的最后一个位置交换,将数组分为三个部分,<x、=x、>x。
现在用的都是快排3.0,时间复杂度O(N*logN),空间复杂度O(logN)。

public static void quickSort(int[] arr) {
	if (arr == null || arr.length < 2) {
		return;
	}
	quickSort(arr, 0, arr.length - 1);
}

public static void quickSort(int[] arr, int l, int r) {
	if (l < r) {
		swap(arr, l + (int) (Math.random() * (r - l + 1)), r);
		int[] p = partition(arr, l, r);
		quickSort(arr, l, p[0] - 1);
		quickSort(arr, p[1] + 1, r);
	}
}

public static int[] partition(int[] arr, int l, int r) {
	int less = l - 1;
	int more = r;
	while (l < more) {
		if (arr[l] < arr[r]) {
			swap(arr, ++less, l++);
		} else if (arr[l] > arr[r]) {
			swap(arr, --more, l);
		} else {
			l++;
		}
	}
	swap(arr, more, r);
	return new int[] { less + 1, more };
}

public static void swap(int[] arr, int i, int j) {
	int tmp = arr[i];
	arr[i] = arr[j];
	arr[j] = tmp;
}

6.堆排序

堆结构:
(1)堆结构就是用数组实现的完全二叉树结构
(2)完全二叉树中如果每棵子树的最大值都在顶部就是大根堆
(3)完全二叉树中如果每棵子树的最小值都在顶部就是小根堆
(4)优先级队列结构底层就是堆结构

public static class MyMaxHeap {
    private int[] heap;
    private final int limit;
    private int heapSize;

    public MyMaxHeap(int limit) {
        heap = new int[limit];
        this.limit = limit;
        heapSize = 0;
    }

    public boolean isEmpty() {
        return heapSize == 0;
    }

    public boolean isFull() {
        return heapSize == limit;
    }

    public void push(int value) {
        if (heapSize == limit) {
            throw new RuntimeException("heap is full");
        }
        heap[heapSize] = value;
        // value heapSize
        heapInsert(heap, heapSize++);
    }

    // 用户此时,让你返回最大值,并且在大根堆中,把最大值删掉
    // 剩下的数,依然保持大根堆组织
    public int pop() {
        int ans = heap[0];
        swap(heap, 0, --heapSize);
        heapify(heap, 0, heapSize);
        return ans;
    }

    // 新加进来的数,现在停在了index位置,请依次往上移动,
    // 移动到0位置,或者干不掉自己的父亲了,停!
    private void heapInsert(int[] arr, int index) {
        // [index] [index-1]/2
        // index == 0
        while (arr[index] > arr[(index - 1) / 2]) {
            swap(arr, index, (index - 1) / 2);
            index = (index - 1) / 2;
        }
    }

    // 从index位置,往下看,不断的下沉
    // 停:较大的孩子都不再比index位置的数大;已经没孩子了
    private void heapify(int[] arr, int index, int heapSize) {
        int left = index * 2 + 1;
        while (left < heapSize) { // 如果有左孩子,有没有右孩子,可能有可能没有!
            // 把较大孩子的下标,给largest
            int largest = left + 1 < heapSize && arr[left + 1] > arr[left] ? left + 1 : left;
            largest = arr[largest] > arr[index] ? largest : index;
            if (largest == index) {
                break;
            }
            // index和较大孩子,要互换
            swap(arr, largest, index);
            index = largest;
            left = index * 2 + 1;
        }
    }

    private void swap(int[] arr, int i, int j) {
        int tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
}

堆排序:
(1)先让整个数组都变成大根堆结构,建立堆的过程
从上到下的方法,时间复杂度为O(N*logN)
从下到上的方法,时间复杂度为O(N)
(2)把堆的最大值和堆末尾的值交换,然后减少堆的大小之后,再去调整堆,一直周而复始,时间复杂度为O(N*logN)
(3)堆的大小减成0之后,排序完成。

public static void heapSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    for (int i = 0; i < arr.length; i++) {
        heapInsert(arr, i);
    }
    int size = arr.length;
    swap(arr, 0, --size);     //把头结点跟堆末尾的值交换
    while (size > 0) {
        heapify(arr, 0, size);
        swap(arr, 0, --size);
    }
}

//上浮操作
public static void heapInsert(int[] arr, int index) {
    while (arr[index] > arr[(index - 1) / 2]) {     //如果子节点的值大于父节点的值,交换
        swap(arr, index, (index - 1) / 2);
        index = (index - 1) / 2;
    }
}

//下潜操作
public static void heapify(int[] arr, int index, int size) {
    int left = 2 * index + 1;     //找到左孩子
    while (left < size) {
        int largest = left + 1 < size && arr[left] < arr[left + 1] ? left + 1 : left;     //如果右孩子也在范围内,并且右孩子比左孩子大,那么较大的那个就是右孩子,否则就是左孩子
        largest = arr[index] > arr[largest] ? index : largest;
        if (largest == index) {
            break;
        }
        swap(arr, largest, index);
        index = largest;
        left = 2 * index + 1;     //继续下潜
    }
}

public static void swap(int[] arr, int i, int j) {
    int temp = arr[i];
    arr[i] = arr[j];
    arr[j] = temp;
}

堆排序扩展题目
image

public static void sortedArrDistanceLessK(int[] arr, int k) {
    if (k == 0) {
        return;
    }
    // 默认小根堆
    PriorityQueue<Integer> heap = new PriorityQueue<>();
    int index = 0;
    // 0...K-1
    for (; index <= Math.min(arr.length - 1, k - 1); index++) {     
        heap.add(arr[index]);
    }
    int i = 0;
    for (; index < arr.length; i++, index++) {
        heap.add(arr[index]);
        arr[i] = heap.poll();
    }
    while (!heap.isEmpty()) {
        arr[i++] = heap.poll();
    }
}

7.计数排序

// only for 0~200 value
public static void countSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(max, arr[i]);
    }
    int[] bucket = new int[max + 1];
    for (int i = 0; i < arr.length; i++) {
        bucket[arr[i]]++;
    }
    int i = 0;
    for (int j = 0; j < bucket.length; j++) {
        while (bucket[j]-- > 0) {
            arr[i++] = j;
        }
    }
}

8.基数排序

// only for no-negative value
public static void radixSort(int[] arr) {
    if (arr == null || arr.length < 2) {
        return;
    }
    radixSort(arr, 0, arr.length - 1, maxbits(arr));
}

public static int maxbits(int[] arr) {
    int max = Integer.MIN_VALUE;
    for (int i = 0; i < arr.length; i++) {
        max = Math.max(max, arr[i]);
    }
    int res = 0;
    while (max != 0) {
        res++;
        max /= 10;
    }
    return res;
}

// arr[L..R]排序  ,  最大值的十进制位数digit
public static void radixSort(int[] arr, int L, int R, int digit) {
    final int radix = 10;
    int i = 0, j = 0;
    // 有多少个数准备多少个辅助空间
    int[] help = new int[R - L + 1];
    for (int d = 1; d <= digit; d++) { // 有多少位就进出几次
        // 10个空间
        // count[0] 当前位(d位)是0的数字有多少个
        // count[1] 当前位(d位)是(0和1)的数字有多少个
        // count[2] 当前位(d位)是(0、1和2)的数字有多少个
        // count[i] 当前位(d位)是(0~i)的数字有多少个
        int[] count = new int[radix]; // count[0..9]
        for (i = L; i <= R; i++) {
            // 103  1   3
            // 209  1   9
            j = getDigit(arr[i], d);
            count[j]++;
        }
        for (i = 1; i < radix; i++) {
            count[i] = count[i] + count[i - 1];
        }
        for (i = R; i >= L; i--) {
            j = getDigit(arr[i], d);
            help[count[j] - 1] = arr[i];
            count[j]--;
        }
        for (i = L, j = 0; i <= R; i++, j++) {
            arr[i] = help[j];
        }
    }
}

public static int getDigit(int x, int d) {
    return ((x / ((int) Math.pow(10, d - 1))) % 10);
}

9.总结

排序方法 时间复杂度 空间复杂度 稳定性
冒泡排序 O(N^2) O(1) 稳定
选择排序 O(N^2) O(1) 不稳定
插入排序 O(N^2) O(1) 稳定
随即快排 O(N*logN) O(logN) 不稳定
堆排序 O(N*logN) O(1) 不稳定
归并排序 O(N*logN) O(N) 稳定
计数排序 O(N) O(N) 稳定
基数排序 O(N) O(N) 稳定

10.排序算法总结

1.不基于比较的排序,对样本数据有严格要求,不易改写
2.基于比较的排序,只要规定好两个样本怎么比大小就可以直接复用
3.基于比较的排序,时间复杂度的极限是O(N*logN)。
4.时间复杂度O(N*logN)、额外空间复杂度低于O(N),且稳定的基于比较的排序是不存在的。
5.为了绝对的速度选快排,为了省空间选堆排,为了稳定性选归并

11.常见的坑

1.归并排序的额外空间复杂度可以变成O(1),但是非常难,不需要掌握,有兴趣可以搜“归并排序 内部缓存法”,但是会变得不稳定
2.“原地归并排序”的帖子都是垃圾,会让归并排序的时间复杂度变成O(N^2)
3.快速排序可以做到稳定性问题,但是非常难,不需要掌握, 可以搜“01stable sort”,但是对样本数据要求更多
4.有一道题目,是奇数放在数组左边,偶数放在数组右边,还要求原始的相对次序不变,时间复杂度做到O(N),额外空间复杂度做到O(1),碰到这个问题,可以怼面试官。(类似于随机快排,就是01partation,原始的随机快排都不是稳定的)

三、二叉树的遍历

1.先序(前序)遍历

  1. 递归方式
public void preOrder(TreeNode root){
    if(root==null){
        return;
    }
    System.out.println(root.val);
    preOrder(root.left);
    preOrder(root.right);
}
  1. 非递归方式
public void preOrder(TreeNode root){
    if(root!=null){
        Stack<TreeNode> stack=new Stack<>();
        stack.add(root);
        while(!stack.isEmpty()){
            root=stack.pop();
            System.out.println(root.val);
            if(root.right!=null){
                stack.push(root.right);           
            }        
            if(root.left!=null){
                stack.push(root.left);            
            }
        }    
    }
}
public void preOrder(TreeNode root){
    Stack<TreeNode> stack=new Stack<>();
    TreeNode cur=root;
    while(!stack.isEmpty()||cur!=null){
        if(cur!=null){
            stack.push(cur);
            System.out.println(cur.val);
            cur=cur.left;        
        }else{
            TreeNode pop = stack.pop();
            cur=pop.right;
        }                    
    }
}

2.中序遍历

  1. 递归方式
public void inOrder(TreeNode root){
    if(root==null){
        return;
    }
    preOrder(root.left);
    System.out.println(root.val);
    preOrder(root.right);
}
  1. 非递归方式
public void inOrder(TreeNode root){
    if(root!=null){
        Stack<TreeNode> stack=new Stack<>();
        while(!stack.isEmpty()||root!=null){
            if(root!=null){
                stack.push(root);
                root=root.left;                                            
            }else{
                root=stack.pop();
                System.out.println(root.val);
                root=root.right;                            
            }
        }    
    }
}
public void inOrder(TreeNode root){
    Stack<TreeNode> stack=new Stack<>();
    TreeNode cur=root;
    while(!stack.isEmpty()||cur!=null){
        if(cur!=null){
            stack.push(cur);
            cur=cur.left;        
        }else{
              TreeNode pop=stack.pop();
              System.out.println(pop.val);
              cur=pop.right;      
        }
    }
}

3.后序遍历

  1. 递归方式
public void postOrder(TreeNode root){
    if(root==null){
        return;
    }
    preOrder(root.left);
    preOrder(root.right);
    System.out.println(root.val);
}
  1. 非递归方式
public void postOrder(TreeNode root){
    if(root!=null){
        Stack<TreeNode> stack1=new Stack<>();
        Stack<TreeNode> stack2=new Stack<>();
        stack1.push(root);
        while(!stack1.isEmpty()){
            root=stack1.pop();
            stack2.push(root);
            if(root.left!=null){
                stack1.push(root.left);            
            }        
            if(root.right!=null){
                stack1.push(root.right);            
            }
        }
        while(!stack2.isEmpty()){
            System.out.println(stack2.pop().val);        
        }
    }
}
public void postOrder(TreeNode root){
    Stack<TreeNode> stack=new Stack<>();
    TreeNode cur=root;
    TreeNode pop = null;
    while(!stack.isEmpty()||cur!=null){
        if(cur!=null){
            stack.push(cur);
            cur=cur.left;        
        }else{
            TreeNode peek=stack.peek();
            if(peek.right==null||peek.right==pop){
                pop=stack.pop();
                System.out.println(pop.val);            
            }else{
                cur=peek.right;           
            }
        }
    }
}

4.如何判断一个二叉树是搜索二叉树

  1. 中序非递归方式
public boolean isValidBST(TreeNode root){
    TreeNode p=root;
    Stack<TreeNode> stack=new Stack<>();
    long prev=Long.MIN_VALUE;
    while(p!=null||!stack.isEmpty()){
        if(p!=null){
            stack.push(p);
            p=p.left;        
        }else{
            TreeNode pop=stack.pop();
            if(prev>=pop.value){
                return false;            
            }        
            prev=pop.value;
            p=pop.right;
        }
    }
    return true;
}
  1. 中序递归方式
public boolean isValidBST(TreeNode root){
    if(root==null){
        return true;    
    }
    return doValidBST(new AtomicLong(Long.MIN_VALUE),root);
}
public boolean doValidBST(AtomicLong prev,TreeNode root){
    if(root==null){
        return true;    
    }
    boolean isValidLeft=doValidBST(prev,root.left);
    if(prev.get()>=root.val){
        reture false;    
    }
    prev.set(root.val);
    boolean isValidRight=doValidBST(prev,root.right);
    return isValidLeft&&isValidRight;
}
  1. 上下限递归
public boolean isValidBST(TreeNode root){
    if(root==null){
        return true;    
    }
    return doValidBST(root,Long.MIN_VALUE,Long.MAX_VALUE);
}
public boolean doValidBST(TreeNode node,Long min,Long max){
    if(node==null){
        return true;    
    }
    if(node.val<=min||node.val>=max){
        return false;    
    }
    return doValidBST(node.left,min,node.val)&&doValidBST(node.right,node.val,max);
}
  1. 递归方式
public static class ReturnData{
    public boolean isBST;    //是否是搜索二叉树
    public int max;    //最大值
    public int min;    //最小值
    
    public ReturnData(boolean isBST,int max,int min){
            this.isBST=isBST;
            this.max=max;
            this.min=min;
    }
}

public static ReturnData process(TreeNode x){
    if(x==null){
        return null;    
    }
    ReturnData leftData=process(x.left);
    ReturnData rightData=process(x.right);
    int min=x.value;
    int max=x.value;
    if(leftData!=null){
        min=Math.min(leftData.min,min);
        max=Math.max(leftData.max,max);    
    }
    if(rightData!=null){
        min=Math.min(rightData.min,min);
        max=Math.max(rightData.max,max);    
    }
    boolean isBST=false;
    if((leftData!=null?(leftData.isBST&&leftData.max<x.value):true) &&(rightData!=null?(rightData.isBST&&rightData.min>x.value):true)){
        isBST=true;    
    }
    return new ReturnData(isBST,max,min);
}

5.如何判断一棵二叉树是完全二叉树

思路:

  • 一个节点有右孩子无左孩子,返回false
  • 在与第一条不冲突的情况下,遇到第一个左右孩子不全的节点后,其后面的节点全是叶子节点
public boolean isCBT(TreeNode root){
    if(root==null){
        return true;    
    }
    boolean leaf=false;    //判断是否遇到第一个左右孩子不全的节点
    LinkedList<TreeNode> queue=new LinkedList<>();
    TreeNode l=null;    //用于表示左孩子
    TreeNode r=null;    //用于表示右孩子
    queue.add(root);
    while(!queue.isEmpty()){
        root = queue.poll();
        l=root.left;
        r=root.right;
        if((leaf&&(l!=null||r!=null)) || (l==null&&r!=null)){
            return false;        
        }
        if(l!=null){
            queue.add(l);                
        }
        if(r!=null){
            queue.add(r);        
        }else{
            leaf=true;        
        }
    }
    return true;
}

6.如何判断一棵二叉树是满二叉树

public static class Info{
    public int height;
    public int nodes;
    
    public Info(int h,int n){
        this.height=h;
        this.nodes=n;    
    }
}

public static Info process(TreeNode x){
    if(x==null){
        return new Info(0,0);    
    }
    Info leftData=process(x.left);
    Info rightData=process(x.right);
    int height=Math.max(leftData.height,rightData.height)+1;
    int nodes=leftData.nodes+rightData.nodes+1;
    return new Info(height,nodes);
}

public static boolean isFull(TreeNode head){
    if(head==null){
        return true;    
    }
    Info data=process(head);
    return data.nodes==((1<<data.height)-1);
}

7.如何判断一棵二叉树是平衡二叉树

public static class ReturnType{
    public int height;
    public boolean isBalanced;
    
    public ReturnType(boolean isBalanced,int height){
        this.isBalanced=isBalanced;
        this.height=height;    
    }
}

public static ReturnType process(TreeNode x){
    if(x==null){
        return new ReturnType(true,0);    
    }
    ReturnType leftData=process(x.left);
    ReturnType rightData=process(x.right);
    int height=Math.max(leftData.height,rightData.height)+1;
    boolean isBalanced=leftData.isBalanced&&rightData.isBalanced&&Math.abs(leftData.height-rightData.height)<2;
    return new ReturnType(isBalanced,height);
}

8.给定两个二叉树的节点node1和node2,找到他们的最低公共祖先节点

public static void process(TreeNode head,HashMap<TreeNode,TreeNode> fatherMap){    //得到每一个节点的父亲节点
    if(head==null){
        return;    
    }
    fatherMap.put(head.left,head);
    fatherMap.put(head.right,head);
    process(head.left,fatherMap);
    process(head.right,fatherMap);
}

public static TreeNode lca(TreeNode head,TreeNode o1,TreeNode o2){
    HashMap<TreeNode> fatherMap=new HashMap<>();
    fatherMap.put(head,head);
    process(head,fatherMap);
    HashSet<TreeNode> set1=new HashSet<>();
    TreeNode cur=o1;
    while(cur!=fatherMap.get(cur)){
        set1.add(cur);
        cur=fatherMap.get(cur);    
    }
    set1.add(head);
    //接下来,遍历o2的父亲节点,判断是否与set1中的相等,返回第一个相等的节点即为最低公共祖先节点,若没有相等的,则返回head
}
//第一种情况,o1是o2的最低公共祖先,或者o2是o1的最低公共祖先
//第二种情况,o1和o2不互为最低公共祖先,需要向上查找
public static TreeNode lowestAncestor(TreeNode head,TreeNode o1,TreeNode o2){
    if(head==null||head==o1||head==o2){
        return head;    
    }
    TreeNode left=lowestAncestor(head.left,o1,o2);
    TreeNode right=lowestAncestor(head.right,o1,o2);
    if(left!=null&&right!=null){
        return head;    
    }
    return left!=null?left:right;
}
//若 p,q 在 a 的两侧,则 ancestor 就是它们的最近公共祖先,此方法只适用于二叉搜索树
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    TreeNode a=root;
    while(p.val<a.val&&q.val<a.val || p.val>a.val&&q.val>a.val){
        if(p.val<a.val){
            a=a.left;
        }else if(p.val>a.val){
            a=a.right;
        }
    }
    return a;
}

9. 在二叉树中找一个节点的后继节点

public TreeNode getSuccessorNode(TreeNode node){
    if(node==null){
        return node;    
    }
    if(node.right!=null){
        return getLeftMost(node.right);    
    }else{
        TreeNode parent = node.parent;
        while(parent!=null&&node!=parent.left){    //当前节点是父亲节点的右孩子
            node=parent;
            parent=node.parent;        
        }
        return parent;
    }
}

//找到最左的孩子
public TreeNode getLeftMost(TreeNode node){
    if(node==null){
        return node;    
    }
    while(node.left!=null){
        node=node.left;    
    }
    return node;
}

10.二叉树的序列化和反序列化

就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树

//将树变成字符串
public String serialByPre(TreeNode head){
    if(head==null){
        return "#_";    
    }
    String res=head.value+"_";
    res+=serialByPre(head.left);
    res+=serialByPre(head.right);
    return res;
}

//将字符串变成树
public TreeNode reconByPreString(String preStr){
    String[] values = preStr.split("_");
    Queue<String> queue=new LinkedList<String>();
    for(int i=0;i<values.length;i++){
        queue.offer(values[i]);    
    }
    return reconPreOrder(queue);
}
public TreeNode reconPreOrder(Queue<String> queue){
    String value=queue.poll();
    if(value.equals("#")){
        return null;   
    }
    TreeNode head=new TreeNode(Integer.parseInt(value));
    head.left=reconPreOrder(queue);
    head.right=reconPreOrder(queue);
    return head;
}

11.折纸问题

请把一段纸条竖着放在桌子上,然后从纸条的下边向上方对折1次,压出折痕后展开。此时折痕是凹下去的,即折痕突起的方向指向纸条的背面。如果从纸条的下边向上方连续对折2次,压出折痕后展开,此时有三条折痕,从上到下依次是下折痕、下折痕和上折痕。给定一个输入参数N,代表纸条都从下边向上方连续对折N次。请从上到下打印所有折痕的方向。
思路:
image

  • 每一次的折纸都在上一次的折痕上方形成一条凹折痕,下方形成一条凸折痕
public void printAllFolds(int N){
    printProcess(1,N,true);
}

//递归过程,来到了某一个节点,i是节点的层数,N一共的层数,down==true 凹    down==false  凸
public void printProcess(int i,int N,boolean down){
    if(i>N){
        return;    
    }
    printProcess(i+1,N,true);
    System.out.println(down?"凹":"凸");
    printProcess(i+1,N,false);
}

12.前中后序的统一写法

public List<Integer> preorderTraversal(TreeNode root) {
    List<Integer> result=new ArrayList<>();
    Stack<TreeNode> stack=new Stack<>();
    if(root!=null){
        stack.push(root);
    }
    while(!stack.isEmpty()){
        TreeNode node=stack.peek();
        if(node!=null){
            stack.pop();
            if(node.right!=null){
                stack.push(node.right);
            }
            if(node.left!=null){
                stack.push(node.left);
            }
            stack.push(node);
            stack.push(null);       //用null来作为标记
        }else{
            stack.pop();    //把栈顶的null标记给弹出
            TreeNode pop=stack.pop();
            result.add(pop.val);
        }
    }
    return result;
}

public List<Integer> inorderTraversal(TreeNode root) {
    List<Integer> result=new ArrayList<>();
    Stack<TreeNode> stack=new Stack<>();
    if(root!=null){
        stack.push(root);
    }
    while(!stack.isEmpty()){
        TreeNode node=stack.peek();
        if(node!=null){
            stack.pop();
            if(node.right!=null){
                stack.push(node.right);
            }
            stack.push(node);
            stack.push(null);       //用null来作为标记
            if(node.left!=null){
                stack.push(node.left);
            }
        }else{
            stack.pop();    //把栈顶的null标记给弹出
            TreeNode pop=stack.pop();
            result.add(pop.val);
        }
    }
    return result;
}

public List<Integer> postorderTraversal(TreeNode root) {
    List<Integer> result=new ArrayList<>();
    Stack<TreeNode> stack=new Stack<>();
    if(root!=null){
        stack.push(root);
    }
    while(!stack.isEmpty()){
        TreeNode node=stack.peek();
        if(node!=null){
            stack.pop();
            stack.push(node);
            stack.push(null);       //用null来作为标记
            if(node.right!=null){
                stack.push(node.right);
            }
            if(node.left!=null){
                stack.push(node.left);
            }
        }else{
            stack.pop();    //把栈顶的null标记给弹出
            TreeNode pop=stack.pop();
            result.add(pop.val);
        }
    }
    return result;
}

四、图

1.图的基础要素的定义

(1)边

public class Edge {
    public int weight;    //边的权重
    public Node from;    //边的起点
    public Node to;    //边的终点
    
    public Edge(int weight, Node from, Node to) {
        this.weight = weight;
        this.from = from;
        this.to = to;
    }
}

(2)节点

public class Node {
    public int value;    //当前节点的数值
    public int in;    //入度
    public int out;    //出度
    public ArrayList<Node> nexts;    //相邻的节点(当前节点是起始点)
    public ArrayList<Edge> edges;    //由该节点发射出去的边
    
    public Node(int value) {
        this.value = value;
        in = 0;
        out = 0;
        nexts = new ArrayList<>();
        edges = new ArrayList<>();
    }
}

(3)图

import java.util.HashMap;
import java.util.HashSet;

public class Graph {
    public HashMap<Integer,Node> nodes;
    public HashSet<Edge> edges;
    
    public Graph() {
        nodes = new HashMap<>();
        edges = new HashSet<>();
    }
}

(4)构建图

public class GraphGenerator{
    public static Graph createGraph(Integer[][] matrix){
        Graph graph=new Graph();
        for(int i=0;i<matrix.length;i++){
            Integer weight=matrix[i][0];  
            Integer from=matrix[i][1];  
            Integer to=matrix[i][2]; 
            if(!graph.nodes.containsKey(from)){
                graph.nodes.put(from,new Node(from));            
            }       
            if(!graph.nodes.containsKey(to)){
                graph.nodes.put(to,new Node(to));            
            }  
            Node fromNode=graph.nodes.get(from);
            Node toNode=graph.nodes.get(to);
            Edge newEdge=new Edge(weight,fromNode,toNode);
            fromNode.nexts.add(toNode);
            fromNode.out++;
            toNode.in++;
            fromNode.edges.add(newEdge);
            graph.edges.add(newEdge);
        }    
        return graph;
    }
}

2.图的宽度优先遍历

思路:

  • 利用队列实现
  • 从源节点开始依次按照宽度进队列,然后弹出
  • 每弹出一个点,把该节点所有没有进过队列的邻接点放入队列
  • 直到队列变空
public static void bfs(Node node){
    if(node==null){
        return;    
    }
    Queue<Node> queue=new Queue<>();
    HashSet<Node> set=new HashSet<>();
    queue.add(node);
    set.add(node);
    while(!queue.isEmpty()){
        Node cur = queue.poll();   
        System.out.println(cur.value);
        for(Node next:cur.nexts){
            if(!set.contains(next)){
                set.add(next);
                queue.add(next);            
            }        
        }
    }
}

3.图的深度优先遍历

思路:

  • 利用栈实现
  • 从源节点开始把节点按照深度放入栈,然后弹出
  • 每弹出一个点,把该节点下一个没有进过栈的邻接点放入栈
  • 直到栈变空
public static void dfs(Node node){
    if(node==null){
        return;    
    }
    Stack<Node> stack=new Stack<>();
    HashSet<Node> set=new HashSet<>();
    stack.add(node);
    set.add(node);
    System.out.println(node.value);
    while(!stack.isEmpty()){
        Node cur=stack.pop();
        for(Node next:cur.nexts){
            if(!set.contains(next)){
                stack.push(cur);
                stack.push(next);
                set.add(next);
                System.out.println(next.value); 
                break;                                                       
            }        
        }    
    }
}

4.拓扑排序算法

适用范围:要求有向图,且有入度为0的节点,且没有环

public static List<Node> sortedTopology(Graph graph){
    HashMap<Node,Integer> inMap=new HashMap<>();    //key:某一个节点    value:剩余的入度
    Queue<Node> zeroInQueue=new LinkedList<>();    //    用于存放入度为0的节点
    for(Node node:graph.nodes.values()){
        inMap.put(node,node.in);
        if(node.in==0){
            zeroInQueue.add(node);        
        }    
    }
    List<Node> result=new ArrayList<>();    //用于存放拓扑排序结果
    while(!zeroInQueue.isEmpty()){
        Node cur=zeroInQueue.poll();
        result.add(cur);
        for(Node next:cur.nexts){
            inMap.put(next,inMap.get(next)-1);
            if(inMap.get(next)==0){
                zeroInQueue.add(next);            
            }        
        }    
    }
    return result;
}

5.kruskal算法(最小生成树算法)

适用范围:要求无向图
特点:从边出发考虑
算法步骤:
1 初始化。将所有边都按权值从小到大排序,将每个节点集合号都初始化为自身编号。
2 按排序后的顺序选择权值最小的边(u,v)。
3 如果节点 u 和 v 属于两个不同的连通分支,则将边(u,v)加入边集 TE 中,并将两个连通分支合并。
4 如果选取的边数小于 n-1,则转向步骤2,否则算法结束。

// Union-Find Set
    public static class UnionFind {
    private HashMap<Node, Node> fatherMap;    //记录父亲节点
    private HashMap<Node, Integer> rankMap;        //记录集合的大小
    
    public UnionFind() {
        fatherMap = new HashMap<Node, Node>();
        rankMap = new HashMap<Node, Integer>();
    }
    
    private Node findFather(Node n) {
        Node father = fatherMap.get(n);
        if (father != n) {    
            father = findFather(father);
        }
        fatherMap.put(n, father);
        return father;
    }
    
    public void makeSets(Collection<Node> nodes) {
        fatherMap.clear();
        rankMap.clear();
        for (Node node : nodes) {
            fatherMap.put(node, node);
            rankMap.put(node, 1);
        }
    }
    
    public boolean isSameSet(Node a, Node b) {
        return findFather(a) == findFather(b);
    }
    
    public void union(Node a, Node b) {
        if (a == null || b == null) {
            return;
        }
        Node aFather = findFather(a);
        Node bFather = findFather(b);
        if (aFather != bFather) {
            int aFrank = rankMap.get(aFather);
            int bFrank = rankMap.get(bFather);
            if (aFrank <= bFrank) {
                fatherMap.put(aFather, bFather);
                rankMap.put(bFather, aFrank + bFrank);
            } else {
                fatherMap.put(bFather, aFather);
                rankMap.put(aFather, aFrank + bFrank);
            }
        }
    }
    }

    public static class EdgeComparator implements Comparator<Edge> {    //根据边的权重升序排列
    @Override
    public int compare(Edge o1, Edge o2) {
        return o1.weight - o2.weight;
    }
    }

    public static Set<Edge> kruskalMST(Graph graph) {
        UnionFind unionFind = new UnionFind();
        unionFind.makeSets(graph.nodes.values());
        PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());    //优先级队列,用于将边进行排序
        for (Edge edge : graph.edges) {
            priorityQueue.add(edge);
        }
        Set<Edge> result = new HashSet<>();
        while (!priorityQueue.isEmpty()) {
            Edge edge = priorityQueue.poll();
            if (!unionFind.isSameSet(edge.from, edge.to)) {    //如果边的两端不属于同一个集合,才能选这条边,选完之后将边的两端加入集合中
                result.add(edge);
                unionFind.union(edge.from, edge.to);
            }
        }
        return result;
    }

6.prim算法(最小生成树算法)

适用范围:要求无向图
特点:从节点出发考虑

public static class EdgeComparator implements Comparator<Edge> {    //将边按照权重升序排列
    @Override
    public int compare(Edge o1, Edge o2) {
        return o1.weight - o2.weight;
    }
}

    public static Set<Edge> primMST(Graph graph) {
    PriorityQueue<Edge> priorityQueue = new PriorityQueue<>(new EdgeComparator());
    HashSet<Node> set = new HashSet<>();        //用于不让节点重复加入
    Set<Edge> result = new HashSet<>();
    for (Node node : graph.nodes.values()) {
        if (!set.contains(node)) {
            set.add(node);
            for (Edge edge : node.edges) {
                priorityQueue.add(edge);
            }
            while (!priorityQueue.isEmpty()) {
                Edge edge = priorityQueue.poll();    //将优先级队列中权重最小的边弹出
                Node toNode = edge.to;    
                if (!set.contains(toNode)) {    //如果边的另一端节点没有加入过set,则这条边可选,将另一端节点加入set,然后再从另一端节点开始选边
                    set.add(toNode);
                    result.add(edge);
                    for (Edge nextEdge : toNode.edges) {    
                        priorityQueue.add(nextEdge);
                    }
                }
            }
        }
    }
    return result;
    }
 
 // 请保证graph是连通图
    // graph[i][j]表示点i到点j的距离,如果是系统最大值代表无路
    // 返回值是最小连通图的路径之和
    public static int prim(int[][] graph) {
        int size = graph.length;
        int[] distances = new int[size];
        boolean[] visit = new boolean[size];
        visit[0] = true;    //选取0节点为初始节点
        for (int i = 0; i < size; i++) {
            distances[i] = graph[0][i];
        }
        int sum = 0;
        for (int i = 1; i < size; i++) {
            int minPath = Integer.MAX_VALUE;
            int minIndex = -1;
            for (int j = 0; j < size; j++) {
                if (!visit[j] && distances[j] < minPath) {
                    minPath = distances[j];
                    minIndex = j;
                }
            }
            if (minIndex == -1) {
                return sum;
            }
            visit[minIndex] = true;
            sum += minPath;
            for (int j = 0; j < size; j++) {
                if (!visit[j] && distances[j] > graph[minIndex][j]) {
                    distances[j] = graph[minIndex][j];
                }
            }
        }
        return sum;
    }

7.Dijkstra算法(单源最短路径算法)

适用范围:没有权值为负数的边(不能有累加和为负数的环)

public static HashMap<Node, Integer> dijkstra1(Node head) {
    //从head出发到所有点的最小距离
    //key:从head出发达到key
    //value:从head出发达到key的最小距离
    //如果在表中,没有T的记录,含义是从head出发到T这个点的距离为正无穷
    HashMap<Node, Integer> distanceMap = new HashMap<>();
    distanceMap.put(head, 0);
    //已经求过距离的节点,存在selectedNodes中,以后再也不碰
    HashSet<Node> selectedNodes = new HashSet<>();
    Node minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
    while (minNode != null) {
        int distance = distanceMap.get(minNode);
        for (Edge edge : minNode.edges) {
            Node toNode = edge.to;
            if (!distanceMap.containsKey(toNode)) {
                distanceMap.put(toNode, distance + edge.weight);
            }
            distanceMap.put(edge.to, Math.min(distanceMap.get(toNode), distance + edge.weight));
        }
        selectedNodes.add(minNode);
        minNode = getMinDistanceAndUnselectedNode(distanceMap, selectedNodes);
    }
    return distanceMap;
}

    public static Node getMinDistanceAndUnselectedNode(HashMap<Node, Integer> distanceMap, HashSet<Node> touchedNodes) {
        Node minNode = null;
        int minDistance = Integer.MAX_VALUE;
        for (Entry<Node, Integer> entry : distanceMap.entrySet()) {
            Node node = entry.getKey();
            int distance = entry.getValue();
            if (!touchedNodes.contains(node) && distance < minDistance) {
                minNode = node;
                minDistance = distance;
            }
        }
        return minNode;
    }

8.Bellman-Ford(含负权图的单源最短路径算法)

public class BellmanFord {
    public static void main(String[] args) {
        // 正常情况
        /*Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");
        Vertex v5 = new Vertex("v5");
        Vertex v6 = new Vertex("v6");

        v1.edges = List.of(new Edge(v3, 9), new Edge(v2, 7), new Edge(v6, 14));
        v2.edges = List.of(new Edge(v4, 15));
        v3.edges = List.of(new Edge(v4, 11), new Edge(v6, 2));
        v4.edges = List.of(new Edge(v5, 6));
        v5.edges = List.of();
        v6.edges = List.of(new Edge(v5, 9));

        List<Vertex> graph = List.of(v4, v5, v6, v1, v2, v3);*/

        // 负边情况
        /*Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");

        v1.edges = List.of(new Edge(v2, 2), new Edge(v3, 1));
        v2.edges = List.of(new Edge(v3, -2));
        v3.edges = List.of(new Edge(v4, 1));
        v4.edges = List.of();
        List<Vertex> graph = List.of(v1, v2, v3, v4);*/

        // 负环情况
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");

        v1.edges = List.of(new Edge(v2, 2));
        v2.edges = List.of(new Edge(v3, -4));
        v3.edges = List.of(new Edge(v4, 1), new Edge(v1, 1));
        v4.edges = List.of();
        List<Vertex> graph = List.of(v1, v2, v3, v4);

        bellmanFord(graph, v1);
    }

    private static void bellmanFord(List<Vertex> graph, Vertex source) {
        source.dist = 0;
        int size = graph.size();
        // 1. 进行 顶点个数 - 1 轮处理
        for (int i = 0; i < size - 1; i++) {
            // 2. 遍历所有的边
            for (Vertex s : graph) {
                for (Edge edge : s.edges) {    //遍历s节点的每一条边
                    // 3. 处理每一条边
                    Vertex e = edge.linked;
                    if (s.dist != Integer.MAX_VALUE && s.dist + edge.weight < e.dist) {//如果s节点的距离不是无穷大,并且s节点的距离加上边的权重小于e节点的距离,就更新e节点的距离,把e节点的上一个节点设为s 
                        e.dist = s.dist + edge.weight;
                        e.prev = s;
                    }
                }
            }
        }
        for (Vertex v : graph) {
            System.out.println(v + " " + (v.prev != null ? v.prev.name : "null"));
        }
    }
}

如果在【顶点-1】轮处理完成后,还能继续找到更短距离,表示发现了负环

9.Floyd-Warshall(多源点之间最短路径的算法)

public class FloydWarshall {
    public static void main(String[] args) {
        Vertex v1 = new Vertex("v1");
        Vertex v2 = new Vertex("v2");
        Vertex v3 = new Vertex("v3");
        Vertex v4 = new Vertex("v4");

        v1.edges = List.of(new Edge(v3, -2));
        v2.edges = List.of(new Edge(v1, 4), new Edge(v3, 3));
        v3.edges = List.of(new Edge(v4, 2));
        v4.edges = List.of(new Edge(v2, -1));
        List<Vertex> graph = List.of(v1, v2, v3, v4);

        /*
                直接连通
                v1  v2  v3  v4
            v1  0   ∞   -2  ∞
            v2  4   0   3   ∞
            v3  ∞   ∞   0   2
            v4  ∞   -1  ∞   0

                k=0 借助v1到达其它顶点
                v1  v2  v3  v4
            v1  0   ∞   -2  ∞
            v2  4   0   2   ∞
            v3  ∞   ∞   0   2
            v4  ∞   -1  ∞   0

                k=1 借助v2到达其它顶点
                v1  v2  v3  v4
            v1  0   ∞   -2  ∞
            v2  4   0   2   ∞
            v3  ∞   ∞   0   2
            v4  3   -1  1   0

                k=2 借助v3到达其它顶点
                v1  v2  v3  v4
            v1  0   ∞   -2  0
            v2  4   0   2   4
            v3  ∞   ∞   0   2
            v4  3   -1  1   0

                k=3 借助v4到达其它顶点
                v1  v2  v3  v4
            v1  0   -1   -2  0
            v2  4   0   2   4
            v3  5   1   0   2
            v4  3   -1  1   0
         */
        floydWarshall(graph);
    }

    static void floydWarshall(List<Vertex> graph) {
        int size = graph.size();
        int[][] dist = new int[size][size];
        Vertex[][] prev = new Vertex[size][size];
        // 1)初始化
        for (int i = 0; i < size; i++) {
            Vertex v = graph.get(i); // v1 (v3)
            Map<Vertex, Integer> map = v.edges.stream().collect(Collectors.toMap(e -> e.linked, e -> e.weight));
            for (int j = 0; j < size; j++) {
                Vertex u = graph.get(j); // v3
                if (v == u) {
                    dist[i][j] = 0;
                } else {
                    dist[i][j] = map.getOrDefault(u, Integer.MAX_VALUE);
                    prev[i][j] = map.get(u) != null ? v : null;
                }
            }
        }
        print(prev);
        // 2)看能否借路到达其它顶点
        /*
            v2->v1          v1->v?
            dist[1][0]   +   dist[0][0]
            dist[1][0]   +   dist[0][1]
            dist[1][0]   +   dist[0][2]
            dist[1][0]   +   dist[0][3]
         */
        for (int k = 0; k < size; k++) {
            for (int i = 0; i < size; i++) {
                for (int j = 0; j < size; j++) {
//                    dist[i][k]   +   dist[k][j] // i行的顶点,借助k顶点,到达j列顶点
//                    dist[i][j]                  // i行顶点,直接到达j列顶点
                    if (dist[i][k] != Integer.MAX_VALUE &&
                            dist[k][j] != Integer.MAX_VALUE &&
                            dist[i][k] + dist[k][j] < dist[i][j]) {
                        dist[i][j] = dist[i][k] + dist[k][j];
                        prev[i][j] = prev[k][j];
                    }
                }
            }
//            print(dist);
        }
        print(prev);
    }

    static void path(Vertex[][] prev, List<Vertex> graph, int i, int j) {
        LinkedList<String> stack = new LinkedList<>();
        System.out.print("[" + graph.get(i).name + "," + graph.get(j).name + "] ");
        stack.push(graph.get(j).name);
        while (i != j) {
            Vertex p = prev[i][j];
            stack.push(p.name);
            j = graph.indexOf(p);
        }
        System.out.println(stack);
    }

    static void print(int[][] dist) {
        System.out.println("-------------");
        for (int[] row : dist) {
            System.out.println(Arrays.stream(row).boxed()
                    .map(x -> x == Integer.MAX_VALUE ? "∞" : String.valueOf(x))
                    .map(s -> String.format("%2s", s))
                    .collect(Collectors.joining(",", "[", "]")));
        }
    }

    static void print(Vertex[][] prev) {
        System.out.println("-------------------------");
        for (Vertex[] row : prev) {
            System.out.println(Arrays.stream(row).map(v -> v == null ? "null" : v.name)
                    .map(s -> String.format("%5s", s))
                    .collect(Collectors.joining(",", "[", "]")));
        }
    }

}

如果在 3 层循环结束后,在 dist 数组的对角线处(i==j 处)发现了负数,表示出现了负环

五、前缀树

何为前缀树?
它属于多叉树结构,典型应用场景是统计、保存大量的字符串,经常被搜索引擎系统用于文本词频统计。它的优点是利用字符串的公共前缀来减少查找时间,最大限度的减少无谓字符串的比较和存储空间

1.前缀树中节点的定义

public static class TrieNode{
    public int pass;    //表示该节点被经过多少次
    public int end;    //表示有多少字符串是以该节点结尾的
    public TrieNode[] nexts;
    
    public TrieNode(){
        pass=0;
        end=0;
        nexts=new TrieNode[26];    //nexts[0]==null   没有走向0节点的路     nexts[0]!=null   有走向0节点的路
    }
}

2.前缀树的定义

public static class Trie{
    private TrieNode root;
    
    public Trie(){
        root = new TrieNode();    
    }
 
    //插入一个字符串
    public void insert(String str){
        if(str==null){
            return;        
        }
        char[] chs=str.toCharArray();    //将字符串转成一个个字符
        TrieNode node=root;
        node.pass++;
        int index=0;    //用于标识是哪个字符,a对应0,b对应1,以此类推
        for(int i=0;i<chs.length;i++){    //从左往右遍历字符
            index=chs[i]-'a';    //得到字符对应的int值
            if(node.nexts[index]==null){    //如果当前节点到index节点没有路径,则新建一个节点
                node.nexts[index]=new TrieNode();            
            }
            node=node.nexts[index];    //node移动到下一个节点
            node.pass++;
        }
        node.end++;
    }
    
    //查询一个字符串之前加入过多少次,
    public int search(String str){
        if(str==null){
            return 0;        
        }    
        char[] chs=str.toCharArray();
        TrieNode node=root;
        int index=0;
        for(int i=0;i<chs.length;i++){
            index=chs[i]-'a';
            if(node.nexts[index]==null){    //为空说明前面没有加入过这个字符串
                return 0;            
            }        
            node=node.nexts[index];
        }
        return node.end;    
    }
    
    //在所有加入的字符串中,有几个是以pre这个字符串作为前缀的
    public int prefixNumber(String pre){
        if(pre==null){
            return 0;        
        }    
        char[] chs=str.toCharArray();
        TrieNode node=root;
        int index=0;
        for(int i=0;i<chs.length;i++){
            index=chs[i]-'a';
            if(node.nexts[index]==null){
                return 0;            
            }        
            node=node.nexts[index];
        }
        return node.pass;
    }
    
    //删除某一个字符串
    public void delete(String str){
        if(search(str)!=0){    //确保树中有该字符串
            char[] chs=str.toCharArray();
            TrieNode node=root;
            node.pass--;
            int index=0;
            for(int i=0;i<chs.length;i++){
                index=chs[i]-'a';
                if(--node.nexts[index].pass==0){    
                    node.nexts[index]=null;   //只有java能这样写,C++需要遍历到底去析构
                    return;             
                }            
                node=node.nexts[index];
            }
            node.end--;
        }    
    }
}

六、贪心算法

贪心算法:在某一个标准下,优先考虑最满足标准的样本,最后考虑最不满足标准的样本,最终得到一个答案的算法,叫做贪心算法。也就是说,不从整体最优上加以考虑,所做出的是在某种意义上的局部最优解。
贪心算法在笔试时的解题套路

  • 实现一个不依靠贪心策略的解法x,可以用最暴力的尝试
  • 脑补出贪心策略A,贪心策略B,贪心策略C.....
  • 用解法x和对数器,去验证每一个贪心策略,用实验的方式得知哪个贪心策略正确
  • 不要去纠结贪心策略的证明
    贪心策略在实现时,经常使用到的技巧
  • 根据某标准建立一个比较器来排序
  • 根据某标准建立一个比较器来组成堆

1.一些项目要占用一个会议室宣讲,会议室不能同时容纳两个项目的宣讲。给你每一个项目开始的时间和结束的时间(给你一个数组,里面是一个个具体的项目),你来安排宣讲的日程,要求会议室进行的宣讲的场次最多。返回这个最多的宣讲场次。

public static class Program{
    public int start;
    public int end;
    
    public Program(int start,int end){
        this.start=start;
        this.end=end;    
    }
}

public static class ProgramComparator implements Comparator<Program>{
    @Override
    public int compare(Program o1,Program o2){
        return o1.end-o2.end;    //按照会议结束时间升序排列    
    }
}

//根据会议的结束时间来贪心
public static int BestArranger(Program[] programs,int start){
    Arrays.sort(programs, new ProgramComparator());
    int result=0;
    for(int i=0;i<programs.length;i++){
        if(programs[i].start>=start){
            result++;
            start=programs[i].end;        
        }    
    }
    return result;
}

2.给定一个字符串类型的数组strs,找到一种拼接方式,使得把所有字符串拼起来之后形成的字符串具有最小的字典序。

public static class MyComparator implements Comparator<String> {
    @Override
    public int compare(String a, String b) {    //a拼b小于b拼a
        return (a + b).compareTo(b + a);
    }
}

public static String lowestString(String[] strs) {
    if (strs == null || strs.length == 0) {
        return "";
    }
    Arrays.sort(strs, new MyComparator());    //根据字典序排序
    String res = "";
    for (int i = 0; i < strs.length; i++) {
        res += strs[i];
    }
    return res;
}

3.一块金条切成两半,是需要花费和长度数值一样的铜板的。比如长度为20的金条,不管切成长度多大的两半,都要花费20个铜板。一群人想整分整块金条,怎么分最省铜板?

例如,给定数组{10,20,30},代表一共三个人,整块金条长度为10+20+30=60。
金条要分成10,20,30三个部分。 如果先把长度60的金条分成10和50,花费60;
再把长度50的金条分成20和30,花费50;一共花费110铜板。
但是如果先把长度60的金条分成30和30,花费60;再把长度30金条分成10和20,花费30;一共花费90铜板。
输入一个数组,返回分割的最小代价。(其实就是构建一个哈夫曼树)

public static int lessMoney(int[] arr) {
    PriorityQueue<Integer> pQ = new PriorityQueue<>();
    for (int i = 0; i < arr.length; i++) {
        pQ.add(arr[i]);
    }
    int sum = 0;
    int cur = 0;
    while (pQ.size() > 1) {
        cur = pQ.poll() + pQ.poll();
        sum += cur;
        pQ.add(cur);
    }
    return sum;
}

4.给定初始资金,怎么使收益最大

image

public static class Node{
    public int p;    //利润
    public int c;    //花费
    
    public Node(int p,int c){
        this.p=p;
        this.c=c;    
    }
}
//按照花费建立小根堆
public static class MinCostComparator implements Comparator<Node>{
    @Override
    public int compare(Node o1,Node o2){
        return o1.c-o2.c;    
    }
}
//按照利润建立大根堆
public static class MaxProfitComparator implements Comparator<Node>{
    @Override
    public int compare(Node o1,Node o2){
        return o2.p-o1.p;    
    }
}

public static int findMaximizedCapital(int k,int W,int[] Profits,int[] Capital){
    Node[] nodes=new Node[Profits.length];
    for(int i=0;i<Profits.length;i++){
        nodes[i]=new Node(Profits[i],Capital[i]);    
    }
    PriorityQueue<Node> minCostQ=new PriorityQueue<>(new MinCostComparator());
    PriorityQueue<Node> maxProfitQ=new PriorityQueue<>(new MaxProfitComparator());
    for(int i=0;i<nodes.length;i++){
        minCostQ.add(nodes[i]);    
    }
    for(int i=0;i<k;i++){
        while(!minCostQ.isEmpty()&&minCostQ.poll().c<=W){        //解锁能做的项目,加入maxProfitQ
            maxProfitQ.add(minCostQ.poll());        
        }    
        if(maxProfitQ.isEmpty()){    //如果maxProfitQ里没东西,说明已经没有项目可做了,返回
            return W;        
        }
        W+=maxProfitQ.poll().p;    //选取能做的项目中利润最高的
    }
    return W;
}

5.一个数据流中,随时可以取得中位数

思路:
(1)构建一个大根堆,一个小根堆,将第一个数放入大根堆
(2)判断当前数跟大根堆堆顶的数哪个大,若当前数≤大根堆堆顶,当前数入大根堆;否则入小根堆
(3)判断两个堆的大小,若size较大的堆跟size较小的堆的差值为2,则将size较大的堆的堆顶数放入size较小的堆。

public static class MedianHolder {
    private PriorityQueue<Integer> maxHeap = new PriorityQueue<Integer>(new MaxHeapComparator());
    private PriorityQueue<Integer> minHeap = new PriorityQueue<Integer>(new MinHeapComparator());

    private void modifyTwoHeapsSize() {
        if (this.maxHeap.size() == this.minHeap.size() + 2) {
            this.minHeap.add(this.maxHeap.poll());
        }
        if (this.minHeap.size() == this.maxHeap.size() + 2) {
            this.maxHeap.add(this.minHeap.poll());
        }
    }

    public void addNumber(int num) {
        if (maxHeap.isEmpty() || num <= maxHeap.peek()) {
            maxHeap.add(num);
        } else {
            minHeap.add(num);
        }
        modifyTwoHeapsSize();
    }

    public Integer getMedian() {
        int maxHeapSize = this.maxHeap.size();
        int minHeapSize = this.minHeap.size();
        if (maxHeapSize + minHeapSize == 0) {
            return null;
        }
        Integer maxHeapHead = this.maxHeap.peek();
        Integer minHeapHead = this.minHeap.peek();
        if (((maxHeapSize + minHeapSize) & 1) == 0) {
            return (maxHeapHead + minHeapHead) / 2;
        }
        return maxHeapSize > minHeapSize ? maxHeapHead : minHeapHead;
    }

}

public static class MaxHeapComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        if (o2 > o1) {
            return 1;
        } else {
            return -1;
        }
    }
}

public static class MinHeapComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        if (o2 < o1) {
            return 1;
        } else {
            return -1;
        }
    }
}

6.N皇后问题

N皇后问题是指在N*N的棋盘上要摆N个皇后,要求任何两个皇后不同行、不同列,
也不在同一条斜线上。
给定一个整数n,返回n皇后的摆法有多少种。
n=1,返回1。
n=2或3,2皇后和3皇后问题无论怎么摆都不行,返回0。
n=8,返回92。

public static int num1(int n){
    if(n<1){
        return 0;    
    }
    int[] record=new int[n];    //record[i]表示第i行的皇后放在了第几列
    return process1(0,record,n);
}

//潜台词:record[0...i-1]的皇后,任何两个皇后一定都不共行,不共列,不共斜线
//目前来到了第i行
//record[0...i-1]表示之前行皇后的摆放位置
//n代表整体一共有多少行
//返回值是摆完所有的皇后,合理的摆法有多少种
public static int process1(int i,int[] record,int n){
    if(i==n){    //终止行
        return 1;    
    }
    int res=0;
    for(int j=0;j<n;j++){    //当前行在第i行,j代表第i行的皇后放在第j列
        //当前i行的皇后,放在j列,会不会和之前[0...i-1]的皇后,不共行不共列或者共斜线
        //如果是,认为有效
        //如果不是,认为无效
        if(isValid(record,i,j)){
            record[i]=j;
            res+=process1(i+1,record,n);                    
        }
    }
    return res;
}

//record[0...i-1]你需要看,record[i...]不需要看
//返回i行皇后,放在了j列,是否有效
public static boolean isValid(int[] record,int i,int j){
    for(int k=0;k<i;k++){
        if(j==record[k]||Math.abs(record[k]-j)==Math.abs(i-k)){    //第一个条件判断是否共列,第二个条件判断是否共斜线
            return false;        
        }    
    }
    return true;
}

优化后的解法:

public static int num2(int n) {
    if (n < 1 || n > 32) {
        return 0;
    }
    int limit = n == 32 ? -1 : (1 << n) - 1;        //若n为9,则新建一个后面9位都是1,前面23位都是0的数
    return process2(limit, 0, 0, 0)
}

//colLim 列的限制,1的位置不能放皇后,0的位置可以
//leftDiaLim 左斜线的限制,1的位置不能放皇后,0的位置可以
//rightDiaLim 右斜线的限制,1的位置不能放皇后,0的位置可以
public static int process2(int upperLim, int colLim, int leftDiaLim, int rightDiaLim) {
    if (colLim == upperLim) {    //base case 表示所有的列都填满了
        return 1;
    }
    int pos = 0;
    int mostRightOne = 0;
    pos = upperLim & (~(colLim | leftDiaLim | rightDiaLim));    //所有候选皇后的位置,都在pos上,1的位置是能放皇后的
    int res = 0;
    while (pos != 0) {
        mostRightOne = pos & (~pos + 1);    //得到候选位置中最右边的那个1
        pos = pos - mostRightOne;
        res += process2(upperLim, colLim | mostRightOne,        //下一个节点在列、左斜线、右斜线上的限制
                (leftDiaLim | mostRightOne) << 1,
                (rightDiaLim | mostRightOne) >>> 1);
    }
    return res;
}

七、暴力递归

暴力递归就是尝试:
(1)把问题转化为规模缩小了的同类问题的子问题
(2)有明确的不需要继续进行递归的条件(base case)
(3)有当得到了子问题的结果之后的决策过程
(4)不记录每一个子问题的解
一定要学会怎么去尝试,因为这是动态规划的基础。

1.汉诺塔问题

打印n层汉诺塔从最左边移动到最右边的全部过程
第一步,把i-1个圆盘从左移到中
第二步,把第i个圆盘从左移动右
第三步,把i-1个圆盘从中移到右

public static void hanoi(int n) {
    if (n > 0) {
        func(n, "left", "right", "mid");
    }
}

public static void func(int rest, String from, String to, String help) {
    if (rest == 1) {
        System.out.println("move 1 from" + from + " to " + to);
    } else {
        func(rest - 1, from, help, to);
        System.out.println("move" + rest + "from" + from + " to " + to);
        func(rest - 1, help, to, from);
    }
}

2.打印一个字符串的全部子序列,包括空字符串

public static void printAllSubsquence(String str) {
    char[] chs = str.toCharArray();
    process(chs, 0);
}

//当前来到i位置,要和不要,走两条路
//之前的选择,所形成的结果,是str
public static void process(char[] chs, int i) {
    if (i == chs.length) {
        System.out.println(String.valueOf(chs));
        return;
    }
    process(chs, i + 1);    //要当前字符的
    char tmp = chs[i];
    chs[i] = 0;
    process(chs, i + 1);    //不要当前字符的路
    chs[i] = tmp;
}

public static void function(String str) {
    char[] chs = str.toCharArray();
    process(chs, 0, new ArrayList<Character>());
}

//当前来到i位置,要和不要,走两条路
//res之前的选择,所形成的列表
public static void process(char[] chs, int i, List<Character> res) {
    if(i == chs.length) {
        printList(res);
    }
    List<Character> resKeep = copyList(res);
    resKeep.add(chs[i]);
    process(chs, i+1, resKeep);    //要当前字符的路
    List<Character> resNoInclude = copyList(res);
    process(chs, i+1, resNoInclude);    //不要当前字符的路
}

3.打印一个字符串的全部排列

public static ArrayList<String> Permutation(String str) {
    ArrayList<String> res = new ArrayList<>();
    if (str == null || str.length() == 0) {
        return res;
    }
    char[] chs = str.toCharArray();
    process(chs, 0, res);
    res.sort(null);
    return res;
}

//str[i...]范围上,所有的字符,都可以在i位置上,后续都去尝试
//str[0..i-1]范围上,是之前做的选择
//请把所有的字符串形成的全排列,加入到res里去
public static void process(char[] chs, int i, ArrayList<String> res) {
    if (i == chs.length) {
        res.add(String.valueOf(chs));
    }
    for (int j = i; j < chs.length; j++) {
        swap(chs, i, j);
        process(chs, i + 1, res);
        swap(chs, i, j);
    }
}

public static void swap(char[] chs, int i, int j) {
    char tmp = chs[i];
    chs[i] = chs[j];
    chs[j] = tmp;
}

4.打印一个字符串的全部排列,要求不要出现重复的排列

public static ArrayList<String> Permutation(String str) {
    ArrayList<String> res = new ArrayList<>();
    if (str == null || str.length() == 0) {
        return res;
    }
    char[] chs = str.toCharArray();
    process(chs, 0, res);
    res.sort(null);
    return res;
}

//str[i...]范围上,所有的字符,都可以在i位置上,后续都去尝试
//str[0..i-1]范围上,是之前做的选择
//请把所有的字符串形成的全排列,加入到res里去
public static void process(char[] chs, int i, ArrayList<String> res) {
    if (i == chs.length) {
        res.add(String.valueOf(chs));
    }
    boolean[] visit = new boolean[26];    //记录字符是否已被尝试过
    for (int j = i; j < chs.length; j++) {
        if (!visit[chs[j] - 'a']) {
            visit[chs[j] - 'a'] = true;
            swap(chs, i, j);
            process(chs, i + 1, res);
            swap(chs, i, j);
        }
    }
}

public static void swap(char[] chs, int i, int j) {
    char tmp = chs[i];
    chs[i] = chs[j];
    chs[j] = tmp;
}

5.给定一个整型数组arr,代表数值不同的纸牌排成一条线。玩家A和玩家B依次拿走每张纸牌,规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌,玩家A和玩家B都绝顶聪明。请返回最后获胜者的分数。

image

public static int win1(int[] arr) {
    if (arr == null || arr.length == 0) {
        return 0;
    }
    return Math.max(f(arr, 0, arr.length - 1), s(arr, 0, arr.length - 1));
}

public static int f(int[] arr, int i, int j) {
    if (i == j) {
        return arr[i];
    }
    return Math.max(arr[i] + s(arr, i + 1, j), arr[j] + s(arr, i, j - 1));
}

public static int s(int[] arr, int i, int j) {
    if (i == j) {
        return 0;
    }
    return Math.min(f(arr, i + 1, j), f(arr, i, j - 1));
}

public static int win2(int[] arr) {
    if (arr == null || arr.length == 0) {
        return 0;
    }
    int[][] f = new int[arr.length][arr.length];
    int[][] s = new int[arr.length][arr.length];
    for (int j = 0; j < arr.length; j++) {
        f[j][j] = arr[j];
        for (int i = j - 1; i >= 0; i--) {
            f[i][j] = Math.max(arr[i] + s[i + 1][j], arr[j] + s[i][j - 1]);
            s[i][j] = Math.min(f[i + 1][j], f[i][j - 1]);
        }
    }
    return Math.max(f[0][arr.length - 1], s[0][arr.length - 1]);
}

6.给你一个栈,请你逆序这个栈,不能申请额外的数据结构,只能使用递归函数。如何实现?

public static void reverse(Stack<Integer> stack) {        //假设栈中的元素从栈顶到栈底是123
    if (stack.isEmpty()) {
        return;
    }
    int i = getAndRemoveLastElement(stack);    //则获取元素的顺序是321,当栈为空的时候,最先返回的是1,所以1就被压到栈底了
    reverse(stack);
    stack.push(i);
}

//用于拿到栈底元素
public static int getAndRemoveLastElement(Stack<Integer> stack) {
    int result = stack.pop();
    if (stack.isEmpty()) {
        return result;
    } else {
        int last = getAndRemoveLastElement(stack);
        stack.push(result);
        return last;
    }
}

7.数字字符串与字母字符串的转化

image

public static int number(String str) {
    if (str == null || str.length() == 0) {
        return 0;
    }
    return process(str.toCharArray(), 0);
}

//i之前的位置,如何转化已经做过决定了
//i位置之后有多少种转化的结果
public static int process(char[] chs, int i) {
    if (i == chs.length) {
        return 1;
    }
    if (chs[i] == '0') {
        return 0;
    }
    if (chs[i] == '1') {
        int res = process(chs, i + 1);    //i自己作为单独的部分,后续有多少种方法
        if (i + 1 < chs.length) {
            res += process(chs, i + 2);    //i和i+1作为单独的部分,后续有多少种方法
        }
        return res;
    }
    if (chs[i] == '2') {
        int res = process(chs, i + 1);    //i自己作为单独的部分,后续有多少种方法
        //i和i+1作为单独的部分并且没有超过26,后续有多少种方法
        if (i + 1 < chs.length && (chs[i + 1] >= '0' && chs[i + 1] <= '6')) {
            res += process(chs, i + 2);
        }
        return res;
    }
    return process(chs, i + 1);
}

8.给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物品不能超过这个重量。返回你能装下最多的价值是多少?

public static int maxValue1(int[] weights, int[] values, int bag) {
    return process1(weights, values, 0, 0, bag);
}

//i之后的货物自由选择,形成的最大价值返回
//alreadyweight:之前做的决定达到的重量
//重量永远不要超过bag
public static int process1(int[] weights, int[] values, int i, int alreadyweight, int bag) {
    if (alreadyweight > bag) {
        return 0;
    }
    if (i == weights.length) {
        return 0;
    }
    return Math.max(
            process1(weights, values, i + 1, alreadyweight, bag),    //不要i号货物
            //要i号货物
            values[i] + process1(weights, values, i + 1, alreadyweight + weights[i], bag));
}

或者

public static int process2(int[] weights,int[] values,int i,int alreadyweight,int alreadyvalue,int bag){
    if(alreadyweight>bag){
        return 0;    
    }
    if(i==weights.length){
        return 0;    
    }
    return Math.max(process2(weights,values,i+1,alreadyweight,alreadyvalue,bag),
    process2(weights,values,i+1,alreadyweight+weights[i],alreadyvalue+values[i],bag))
}
posted @ 2024-04-26 11:31  死不悔改奇男子  阅读(6)  评论(0编辑  收藏  举报