薯薯算法日记

1.交换两个数用位运算

public static void main(String[] args) {
        int a = 1;
        int b = 2;
        a = a ^ b;
        b = a ^ b;
        a = a ^ b;
        System.out.println("a"+ "b");

    }

2.用位运算 去求取一组数中出现奇数次的数

如果有两个出现奇数次的数 那我们可以通过 找出 这两个数异或的最右边的1 比如 异或一遍后得到101010(也就是两个出现奇数次的数的异或)此时我们知道 在最后一个位出现1 表示 这两个数在这一位的数字不一样 那么我们再去把这个得到的数 从原来的数组中 去异或一遍这一位和他相同的数字因为出现偶数次的回消掉 那么我们最终得到的 就是其中一个数 代码如下

public static void printOddTimesNum2(int[] arr) {
        //异或可以得到出现奇数次的数 或者求异或合
        int eor = 0;
        for (int i = 0; i < arr.length; i++) {
            eor ^= arr[i];
        }
        //一个数与(&)自己取反+1 取出最右边的1
        int rightOne = eor & (~eor + 1);
        int onlyOne = 0;
        for (int cur : arr) {
            if ((cur & rightOne) == 0) {
                onlyOne ^= cur;
            }
        }
    }

3.求数组区间最大值

本质上 我们用到的递归思想 对原数组进行分块 分成左右两块 然后递归 

比如我们一段数组  8 5 4 7 1 5 6 4 8 我们会先分成两组 8 5 4 7 1 和 5 6 4 8 再次递归 得到 (8 5 4)(7 1 )(5 6 ) ( 4 8)再次递归 得到 然后返回 (8)(7),(6)(8)再向上(8)(8)

最后得到8

 public static int process(int[] arr, int L, int R) {
        //求啊arr[l-r]上求最大值
        if (L == R) {
            return arr[L];
        }
        int mid = L + ((R - L) >> 1);
        int leftMax = process(arr, L, mid);
        int rightMax = process(arr, mid + 1, R);
        return Math.max(leftMax, rightMax);
    }

4.归并排序 我们本质上 是把数组不断递归分成两个部分 然后 开辟一个新的空间 从左右两个数组中 依次拿取最小的数 放入新数组中 然后最后再把新数组拷贝到原数组 例如(1 5 7 8 6 4 9 5 )我们会把他分成(1 5 7 8)(6 4 9 5 )巴拉巴拉依次递归比如我们现在得到(1 5 7 8)(4 5 6 9)两个数组 我们先从两个数组第一个数进行比较1<4 所以我们新数组的第一个数就是 (1)然后左边指针移动到5 右边不动 5>4所以新数组就是(1 4)如果相等 优先拷贝左边的数(右边也行哈哈)代码如下

merge的过程就是一个排序的过程

public static void process1(int[] arr, int L, int R) {
        if (L == R) {
            return;
        }
        int mid = L + ((R - L) >> 1);
        process1(arr, L, mid);
        process1(arr, mid + 1, R);
        merge(arr, L, mid, R);
    }

    //归并排序
    public static void merge(int[] arr, int L, int M, int R) {
        //假设现在l-m,m-r都有序 现在使l-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 < arr.length; i++) {
            arr[L + i] = help[i];
        }
    }

下面是归并排序的一个应用拓展

小和问题就是比如一个数组(1 3 4 5 7 8 2 9 7)后一个数比前一个数大 那么小和就加这个数

比如 有8个数比1大 那么就是1*8+3*6+4*5+5*4+......依次这样一个算法 那么 我们可以用归并排序来处理这个问题 因为我们是递归 所以比如 最后我们得到(1)(3)这两个小组 那么这两个小组再merge的过程中 我们判断1<3那么我们会先把1放到新数组 并且 我们会记录1*1,再往后例如我们得到(1 3 )(4 5 )两个小组我们依然是会先得到1*2 然后再得到3*2 我们只要在右边数组中找到比左边数组指针大的数,我们用可以用右边数组最右边的索引减去右边数组当前索引得到个数

需要注意的是我们之所以不会重算是因为我们只有在merge的过程中才计算小和 也就是再合并数组的过程中 才去计算小和的值 所以我们并不会例如3比1大计算两次 另外因为我们是建立在归并排序的基础上所以我们可以快速算出有几个比1大的数 只不过需要注意的是 此时我们遇到相等情况 必须要先拷贝右边数组中的数 否则我们将无法计算 代码如下 (我也不知道为什么这idea没有颜色好气人)

//小和问题
    //也可以用来求逆序对
    public static int smallSum(int[] arr) {
        if (arr == null || arr.length < 2) {
            return 0;
        }
        return process2(arr, 0, arr.length - 1);
    }

    public static int process2(int[] arr, int l, int r) {
        if (l == r) {
            return 0;
        }
        int mid = l + ((r - l) >> 1);
        return process2(arr, l, mid)
                +
                process2(arr, mid + 1, r)
                +
                merge1(arr, l, mid, r);
    }

    public static int merge1(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;
        int res = 0;
        while (p1 <= m && p2 <= r) {
            res += arr[p1] < arr[p2] ? (r - p2 + 1) * arr[p1] : 0;
            help[i++] = arr[p1] < arr[p2] ? arr[p1++] : arr[p2]++;
        }
        while (p1 <= m) {
            help[i++] = arr[p1++];
        }
        while (p2 <= m) {
            help[i++] = arr[p2++];
        }
        return res;
    }

4.sort快排引入

给定一个数组 和一个数num 要求小于这个数的放在数组左边 大于这个数的放在数组右边 等于放中间

逻辑1)[i]<num [i]和小于区域下一个做交换,小于区域右阔,i++(小于区域起始值0,大于区域起始值最后index+1)

2)[i]==num i++

3)[i]>num,[i]和大于区域前一个做交换,大于区左阔,i不变

比如给定数组(3 5 6 3 4 5 2 6 9 0)num=5

1. 3<5 那么小于区域划分到3

2. 5=5 所以i 不变 

3.5<6 所以6和0做交换 然后大于区域左阔一个(3 5 0 3 4 5 2 6 9 6)大于区域给到6 i 要原地不动因为此时0是新换过来的 再次判断0 此时 0<5 所以0和小于区域下一个index(5)做交换 得到(3 0 5......)然后再次判断5 依次得到答案

快排1.0 取数组最后一个数 对数组进行如上操作 然后 最后一个数和大于区域第一个数做交换 此时就得到了我们刚才取数的数在排序好的数组中应在的位置(当然 也是一个不断递归的过程)(但是我们发现这种方法局限性就是 我们每次取最后一个数 如果这个数一直都是最大的 那么我们就进行了很多无用功)

快排2.0 进行改进 就是我们随机取数组中的一个数当作num 进行如上操作 实际上就是一个概率问题 

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);// <区 p[0]等于区域左边界
            quickSort(arr, p[1] + 1, R);// >区p[1]等于区域右边界
        }
    }

    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};
    }

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值