zcy算法入门笔记004

  1. 路径总和

判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false 。

public static class TreeNode {
        public int val;
        public TreeNode left;
        public TreeNode right;

        TreeNode(int val) {
            this.val = val;
        }
    }

    public static boolean isSum = false;

    public static boolean hasPathSum(TreeNode root, int sum) {
        if (root == null) {
            return false;
        }
        isSum = false;  //先把全局公共变量改false
        process(root, 0, sum);
        return isSum;
    }

    public static void process(TreeNode x, int preSum, int sum) {//之前已经形成的和是preSum,目标是sum
        //把base case不定成空节点,而是叶节点
        if (x.left == null && x.right == null) {
            if (x.val + preSum == sum) {
                isSum = true;
            }
            return;
        }
        // x是非叶节点
        preSum += x.val;
        //保证传入process里的x不能有null的时刻
        if (x.left != null) {
            process(x.left, preSum, sum);
        }
        if (x.right != null) {
            process(x.right, preSum, sum);
        }
    }

  1. 路径总和 II

给你二叉树的根节点 root 和一个整数目标和 targetSum ,找出所有从根节点到叶子节点路径总和等于给定目标和的路径。

注意:达标后返回上层要清理现场。

public static List<List<Integer>> pathSum(TreeNode root, int sum) {//收集所有路径:list套list
        List<List<Integer>> ans = new ArrayList<>();
        if (root == null) {
            return ans;
        }
        ArrayList<Integer> path = new ArrayList<>();
        process(root, path, 0, sum, ans);
        return ans;
    }
    //五个参数:当前到达的某个节点、存之前经过节点的list、之前的累加和、目标数、达标路径存储list
    public static void process(TreeNode x, List<Integer> path, int preSum, int sum, List<List<Integer>> ans) {
        // base case还是叶节点
        if (x.left == null && x.right == null) {
            if (preSum + x.val == sum) {
                path.add(x.val);
                ans.add(copy(path));
                //经典的清理现场的过程:把叶子节点值从list中去掉
                path.remove(path.size() - 1);
            }
            return;
        }
        // x 非叶节点
        path.add(x.val);    //生成路径list
        preSum += x.val;    //累加和是值传递进递归故不用恢复现场,path是按引用传递
        if (x.left != null) {
            process(x.left, path, preSum, sum, ans);
        }
        if (x.right != null) {
            process(x.right, path, preSum, sum, ans);
        }
        //该结点也要向上返回了,把值从list中去掉
        path.remove(path.size() - 1);
    }

    public static List<Integer> copy(List<Integer> path) {
        //copy出一个跟path里值完全一样的list
        List<Integer> ans = new ArrayList<>();
        for (Integer num : path) {
            ans.add(num);
        }
        return ans;
    }

  1. 归并排序

归并排序是建立在归并操作上的一种有效,稳定的排序算法,该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。将已有序的子序列合并,得到完全有序的序列;即先使每个子序列有序,再使子序列段间有序。若将两个有序表合并成一个有序表,称为二路归并。

时间复杂度:O(n * log n)

发明者:约翰·冯·诺伊曼

递归

// 递归方法实现
    public static void mergeSort1(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }

    // arr[L...R]范围上,请让这个范围上的数,有序!
    public static void process(int[] arr, int L, int R) {
        if (L == R) {   //只有一个数
            return;
        }
        // int mid = (L + R) / 2
        // 若下标L+R很大可能发生越界,所以换成以下安全写法
        int mid = L + ((R - L) >> 1);
        process(arr, L, mid);       //左边有序
        process(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
            help[i++] = arr[p1] <= arr[p2] ? arr[p1++] : arr[p2++];
        }
        // 要么p1越界,要么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];
        }
    }

非递归:

步长递增:1->2->4->8->等等 ,然后每步进行两两归并。

注意:

  1. 步长>=总长度时,左组有且已经有序,右边没有,停止。

  1. 对最后一组,右组有多少算多少,进行merge。

  1. 找mid时,如果左组能凑够个数则mid=L+step-1,凑不够应该为mid=N-1。

  1. 但是mid=L+step-1可能发生值越界,计算:N-1-L+1=N-L个数。故N - L >= step时,mid=L+step-1;否则M = N - 1(此时没有右组了)。

  1. 若有右组,计算N-1-(M+1)+1=N-M-1个数。当N-M-1>=step,则R=M+step;否则R=N-1。

  1. 步长*=2可能发生溢出,若step > N / 2则应该break。一种情况;如果step = N / 2,此时若有17个数,17/2然后向下取整为8,步长为8时break明显会出错。

public static void mergeSort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int step = 1;
        int N = arr.length;
        while (step < N) {  //步长要小于总长度,等于总长度代表左边长度=步长,右边没有则不用继续
            int L = 0;
            while (L < N) { //每次改变步长都划分左右组
                int M = 0;
                //最后的左组可能凑不够,此时不用merge
                //1.可凑满,来到L+step-1
                //2.凑不满,来到N-1就可以了
                //3.逻辑上是Math.min(L+step-1,N-1)
                //4.但是L+step-1可能溢出
                /**
                 * 来到一个具体的L,算L->N-1能否凑够step个数,凑不够M来到N-1,
                 * 能凑够来到L+step-1
                 */
                if (N - L >= step) {    //N-1-L+1>=step 能凑满
                    M = L + step - 1;
                } else {
                    M = N - 1;
                }
                //L...M   此时代表没有右组了
                if (M == N - 1) {
                    break;
                }
                //有右组
                int R = 0;
                //R1...R
                if (N - 1 - M >= step) {    //N-1-(M+1)+1=N-M-1
                    R = M + step;
                } else {
                    R = N - 1;
                }
                //左组:L...M  右组:M+1...R
                merge(arr, L, M, R);
                if (R == N - 1) {
                    break;
                } else {
                    L = R + 1;
                }
            }
            if (step > N / 2) { //为了防止数过大溢出
                break;
            }
            step *= 2;
        }

    }

非递归经典写法:

    public static void mergeSort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        int N = arr.length;
        int mergeSize = 1;
        while (mergeSize < N) {
            int L = 0;
            while (L < N) {
                if (mergeSize >= N - L) {
                    break;
                }
                int M = L + mergeSize - 1;
                int R = M + Math.min(mergeSize, N - M - 1);
                merge(arr, L, M, R);
                L = R + 1;
            }
            if (mergeSize > N / 2) {
                break;
            }
            mergeSize <<= 1;
        }
    }
  1. 快速排序

单趟代码:

public static void splitNum1(int[] arr) {
        int lessEqualR = -1;    //<=区域的右边界,左边是<=区域
        int index = 0;
        int N = arr.length;
        while (index < N) {
            if (arr[index] <= arr[N - 1]) {
                //当前数<=指定数,当前数和<=区下一个数做交换,<=区右扩,当前数也跳下一个
                swap(arr, ++lessEqualR, index++);
            } else {    //当前数>指定数,当前数直接下一个
                index++;
            }
        }
    }

单趟代码优化:

拿最后一个数P做划分;小于区初始为-1;大于区初始为最后一个数。

  1. 当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个;

  1. 当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动;

  1. 当前数=P,直接跳下一个;

  1. 最后P和>区最左边数交换

public static void splitNum2(int[] arr) {   //优化
        int N = arr.length;
        int lessR = -1;     //小于区初始为-1
        int moreL = N - 1;  //大于区初始为最后一个数
        int index = 0;      //当前数
        // arr[N-1] --> 作为划分值
        while (index < moreL) { //index没有撞上大于区的左边界时继续
            if (arr[index] < arr[N - 1]) {  //当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个
                swap(arr, ++lessR, index++);
            } else if (arr[index] > arr[N - 1]) {   //当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动
                swap(arr, --moreL, index);
            } else {
                index++;    //当前数=P,直接跳下一个
            }
        }
        swap(arr, moreL, N - 1);    //最后P和>区最左边数交换
    }

递归实现快排:

// arr[L...R]范围上,拿arr[R]做划分值,
    // L....R < = >
    public static int[] partition(int[] arr, int L, int R) {
        int lessR = L - 1;  //小于区右边界
        int moreL = R;      //大于区左边界
        int index = L;      //当前数
        while (index < moreL) {
            if (arr[index] < arr[R]) {      //index没有撞上大于区的左边界时继续
                swap(arr, ++lessR, index++);    //当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个
            } else if (arr[index] > arr[R]) {   //当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动
                swap(arr, --moreL, index);
            } else {
                index++;     //当前数=P,直接跳下一个
            }
        }
        swap(arr, moreL, R);     //最后P和>区最左边数交换
        //返回等于区的边界
        return new int[] { lessR + 1, moreL };  //小于区的下一个即等于区的第一个,moreL为等于区最后一个
    }

    public static void quickSort1(int[] arr) {  //递归快排
        if (arr == null || arr.length < 2) {
            return;
        }
        process(arr, 0, arr.length - 1);
    }

    public static void process(int[] arr, int L, int R) {
        if (L >= R) {
            return;
        }
        //equalE[0]->等于区域第一个数   equalE[1]->等于区域第二个数
        int[] equalE = partition(arr, L, R);
        process(arr, L, equalE[0] - 1);     //左边排序
        process(arr, equalE[1] + 1, R);     //右边排序
    }

非递归快排:

明确排序的任务:

// arr[L...R]范围上,拿arr[R]做划分值,
    // L....R < = >
    public static int[] partition(int[] arr, int L, int R) {
        int lessR = L - 1;  //小于区右边界
        int moreL = R;      //大于区左边界
        int index = L;      //当前数
        while (index < moreL) {
            if (arr[index] < arr[R]) {      //index没有撞上大于区的左边界时继续
                swap(arr, ++lessR, index++);    //当前数<P,当前数和<区下一个数做交换,<区右扩,当前数跳下一个
            } else if (arr[index] > arr[R]) {   //当前数>P,当前数和>区前一个数做交换,>区左扩,当前数不动
                swap(arr, --moreL, index);
            } else {
                index++;     //当前数=P,直接跳下一个
            }
        }
        swap(arr, moreL, R);     //最后P和>区最左边数交换
        //返回等于区的边界
        return new int[] { lessR + 1, moreL };  //小于区的下一个即等于区的第一个,moreL为等于区最后一个
    }
public static class Job {   //任务类:由一个左边界和一个右边界组成一个任务
        public int L;
        public int R;

        public Job(int left, int right) {
            L = left;
            R = right;
        }
    }
public static void quickSort2(int[] arr) {
        if (arr == null || arr.length < 2) {
            return;
        }
        Stack<Job> stack = new Stack<>();   //将要做的任务放到栈里去
        stack.push(new Job(0, arr.length - 1));
        while (!stack.isEmpty()) {
            Job cur = stack.pop();
            int[] equals = partition(arr, cur.L, cur.R);    //依然返回等于区的位置
            if (equals[0] > cur.L) { // 有 < 区域
                stack.push(new Job(cur.L, equals[0] - 1));  //到等于区域的前一个位置
            }
            if (equals[1] < cur.R) { // 有 > 区域
                stack.push(new Job(equals[1] + 1, cur.R));  //从等于区域的下一个位置
            }
        }
    }

测试代码:

// 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 int[] copyArray(int[] arr) {
        if (arr == null) {
            return null;
        }
        int[] res = new int[arr.length];
        for (int i = 0; i < arr.length; i++) {
            res[i] = arr[i];
        }
        return res;
    }

    // for test
    public static boolean isEqual(int[] arr1, int[] arr2) {
        if ((arr1 == null && arr2 != null) || (arr1 != null && arr2 == null)) {
            return false;
        }
        if (arr1 == null && arr2 == null) {
            return true;
        }
        if (arr1.length != arr2.length) {
            return false;
        }
        for (int i = 0; i < arr1.length; i++) {
            if (arr1[i] != arr2[i]) {
                return false;
            }
        }
        return true;
    }

    // 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[] arr = { 7, 1, 3, 5, 4, 5, 1, 4, 2, 4, 2, 4 };
//
//        splitNum2(arr);
//        for (int i = 0; i < arr.length; i++) {
//            System.out.print(arr[i] + " ");
//        }

        int testTime = 500000;
        int maxSize = 100;
        int maxValue = 100;
        boolean succeed = true;
        System.out.println("test begin");
        for (int i = 0; i < testTime; i++) {
            int[] arr1 = generateRandomArray(maxSize, maxValue);
            int[] arr2 = copyArray(arr1);
//            int[] arr3 = copyArray(arr1);
            quickSort1(arr1);
            quickSort2(arr2);
//            quickSort3(arr3);
            if (!isEqual(arr1, arr2) /*|| !isEqual(arr1, arr3)*/) {
                System.out.println("Oops!");
                succeed = false;
                break;
            }
        }
        System.out.println("test end");
        System.out.println(succeed ? "Nice!" : "Oops!");

    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值