排序算法总结

50. Pow(x, n)(快速幂)

注意:以下算法为暴力法,时间复杂度为O(n),最后结果是超时了,那么想想能不能降低以下时间复杂度。

class Solution {
    public double myPow(double x, int n) {
        /**
        分析:
        题目要求实现pow函数,最直接的思路就是暴力遍历。结果是时间复杂度超时。
         */
        // return Math.pow(x,n);
        // 对负数进行预处理
        long b = n;
        if( b < 0){
            x = 1 / x;
            b = -b;
        }
        double res = 1;
        for(int i = 0; i < b; i ++){
            res *= x;
        }
        return res;

    }
}

注意:我们可以思考这样一个问题,计算2^10一般思路是2循环10次,那么可不可以使用 2 ^ 5 * 2 ^ 5呢?然后依次递归,答案肯定是可以的,那么时间复杂度就降低为O(logn)了,在实现过程了,会发现一个问题,递归过程中n是奇数或者偶数,对结果影响蛮大的,那么就要分治考虑了。

class Solution {
    public double myPow(double x, int n) {
        /**
        分析:
        题目要求实现pow函数,最直接的思路就是暴力遍历。结果是时间复杂度超时。
         */
        // return Math.pow(x,n);
        // 对负数进行预处理
        int  b = n;
        if( b < 0){
            x = 1 / x;
            b = -b;
        }
        return qickPow(x,b);

    }
    public double qickPow(double x,int n){
        // 定义递归出口
        if(n == 0){
            return 1.0;
        }
        // 定义递归函数
        double y = qickPow(x,n / 2);
        // 返回递归结果(这里采用分治的思想,奇数和偶数采用不同的算法)
        return n % 2 == 0 ? y * y : x * y * y;
    }
}

注意:上面是递归的柿子,那么可不可以使用迭代法???肯定可以,有递归就有迭代!!!
取余数 n % 2 等价于 判断二进制最右位 n & 1;
向下整除 n // 2 等价于 右移一位 n >> 1 ;

class Solution {
    public double myPow(double x, int n) {
        /**
        分析:
        题目要求实现pow函数,最直接的思路就是暴力遍历。结果是时间复杂度超时。
         */
        // return Math.pow(x,n);
        // 对负数进行预处理
        long  b = n;
        if( b < 0){
            x = 1 / x;
            b = -b;
        }
        double res = 1;
        while( b != 0){
            if(( b & 1) == 1){
                // 发现二进制末尾是奇数
                res *= x;
            }
            x *= x;
            b >>= 1;
        }
        return res;

    }
}

56. 合并区间(排序+双指针)

class Solution {
    public int[][] merge(int[][] intervals) {
        /**
        分析:
        第一想法是先对数组排好序,然后使用双指针,按照规则进行合并。
        这里其实更多考察的是api的调用,比如二维数组排序使用lambada表达式,二维数组如何截取字符串等等操作。
        按照规则合并这里,是使用了一个小技巧:先判断区间,然后再合并,更新双指针,最后进行最后一位的合并判断。这个思路很有用,不要一下子把事情做完,而是分步骤。
        
         */
         // 定义一个区间计数器
         int count = 0;
         // 按照第一个数字大小排序
         Arrays.sort(intervals,(a,b) -> {return a[0] - b[0];});
         // 初始化双指针
         int start = intervals[0][0], end = intervals[0][1];
         // 定义结果数组
         int [][]res = new int[intervals.length][2];
         // 特判
         if(intervals.length == 1){
             return intervals;
         }
         // 遍历数组
         for(int i = 1; i < intervals.length; i++){
             // 判定下一个区间的start是否小于上一个区间的end
             if(intervals[i][0] <= end){
                 // 更新最大的end
                 end = Math.max(end,intervals[i][1]);
             }else{
                 // 合并区间
                 res[count][0] = start;
                 res[count][1] = end;
                 count++;
                 // 双指针指向新的区间
                 start = intervals[i][0];
                 end = intervals[i][1];
             }
         }
         // 合并最后一个数
         res[count][0] = start;
         res[count][1] = end;
         count++;
         // 截取结果数组
         return Arrays.copyOfRange(res,0,count);

    }
}

57. 插入区间(排序+双指针)

注意:这里的思路和上一题大体一样,就是把新的一维数组插入到原有数组中,然后排序,最后按照上一题双指针的解法。

class Solution {
    public int[][] insert(int[][] intervals, int[] newInterval) {
        /**
        分析:
        是上一题合并区间的变种题。循环遍历,按照规则插入即可!
         */
         // 定义储蓄newinteveral的数组
         int[][] res = new int[intervals.length + 1][2];
         // 将数组复制到新数组
         for(int i = 0; i < intervals.length; i++){
             for(int j = 0; j < intervals[0].length; j++){
                 res[i][j] = intervals[i][j];
             }
         }
        //  res = Arrays.copyOfRange(intervals,0,intervals.length+1);
         res[intervals.length][0] = newInterval[0];
         res[intervals.length][1] = newInterval[1];
         // 排序
         Arrays.sort(res,(a,b) ->{return a[0] - b[0];});
         // 定义计数
         int count = 0;
         // 定义最终数组
         int[][] ret = new int[intervals.length + 1][2];
         // 初始化双指针
         int start = res[0][0], end = res[0][1];
         // 遍历数组
         for(int i = 1; i < res.length; i++){
             if(res[i][0] <= end){
                 end = Math.max(end,res[i][1]);
             }else{
                 ret[count][0] = start;
                 ret[count][1] = end;
                 count++;
                 start = res[i][0];
                 end = res[i][1];
             }
         }
         ret[count][0] = start;
         ret[count][1] = end;
         count++;
         return Arrays.copyOfRange(ret,0,count);
         

    }
}

75. 颜色分类(冒泡排序)

class Solution {
    public void sortColors(int[] nums) {
        /**
        分析:
        题目中规定了原地排序,我们知道,这里就是手写冒泡排序了
         */
        // Arrays.sort(nums);
        for(int i = 0; i < nums.length - 1; i++){
            for(int j = i + 1; j < nums.length; j++){
                if(nums[i] > nums[j]){
                    int temp = nums[i];
                    nums[i] = nums[j];
                    nums[j] = temp;
                }
            }
        }
    }
}

88. 合并两个有序数组(逆向双指针法)

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        /**
        分析:
        第一想法是使用双指针法.唯一同的点是本道题需要使用逆向双指针。
         */
        // 定义逆向双指针
        int p1 = m - 1;
        int p2 = n - 1;
        // 结尾指针
        int index = m + n - 1;
        while(p2 >= 0){
            // 判断尾部谁大
            if(p1 >= 0 && nums1[p1] > nums2[p2]){
                // 大的数进入index下标中
                nums1[index] = nums1[p1];
                // 移动指针
                p1--;
            }else{
                nums1[index] = nums2[p2];
                p2--;
            }
            // 更新index
            index--;
        }
    }
}

注意:另一种解法就是插入数组,然后直接调用排序api,面试官可能会diss你,不过是一个好的方法。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int j = 0;
        for(int i = m; i < m + n; i++){
            nums1[i] = nums2[j++];
        }
        Arrays.sort(nums1);
    }
}

179. 最大数(lambada表达是和比较器)

注意:挺有意思的一道题,想要实现不同的比较排序,就要去实现比较器,java 8 可以使用lambada表达柿子。
compareable 是内部排序,也叫做自然排序,是可以自己和别人比较的,但是只能比较一次。实现的方法是compareTo方法,一般默认的类都是使用compareable(比如String和Integer)
comparator是外部排序,也叫做定制排序,可以你叫两个类型大小。实现的方法是compare方法

class Solution {
    public String largestNumber(int[] nums) {
        /**
        分析:
        首先得意识到这是一道排序题。一般而言,Arrays.sort()是可以做到升序排序的,但是不可以降序排序。
        那么要实现降序排序或者对象的自定义排序,一般有两种办法,实现comparable接口或者comparator接口。
        两个接口的不同点在于,comparable可以是内部排序,comparator可以是外部排序。comparable接口实现compareTo方法(比较的是自身和新的对象o),comparator接口实现的是compare方法(比较的是两个对象o)
        基于以上的理解,可以用使用java 8的lambada表达式进行简化操作。
        按照ansic从大到小进行排序。
         */
         // 将int转换为string
         String[] str = new String[nums.length];
         // 储存到str中
         for(int i = 0; i < nums.length; i++){
             str[i] = String.valueOf(nums[i]);
         }
         // 排序,这里是降序排序,所以是 b + a 比较 a + b
         // 这里重点学习一下lambada表达式的写法
         // 传入两个参数,a 和 b,如果想升序排序 那么就是 a - b ,想降序排序就是 b - a;
         // 这里的compareTo函数是String类的一个函数,用于进行字典排序,其实就是重写了compareable接口的compareTo方法
         Arrays.sort(str,(a,b) -> {return (b+a).compareTo(a+b);});
         // 特判
         if(str[0].equals("0")){
             return "0";
         }
         // 定义结果集
         StringBuilder sb = new StringBuilder();
         for(String s:str){
             sb.append(s);
         }
         return sb.toString();


    }
}

628. 三个数的最大乘积(排序+边界条件)

class Solution {
    public int maximumProduct(int[] nums) {
        /**
        分析:
        看起来很简单,实际上有很多边界条件需要判断。
        先排好序。
        若全是正数,那么最大乘积就是后面三个正数了。
        若全是负数,那么最大乘积就是后面三个负数了。
        若有正有负,那么最大乘积就是前两个负数和最后一个正数了

         */
         Arrays.sort(nums);
         int len = nums.length;
         return Math.max(nums[0]*nums[1]*nums[len - 1],nums[len - 1]*nums[len - 2]* nums[len - 3]);
         

    }
}

905. 按奇偶排序数组(快速排序)

class Solution {
    public int[] sortArrayByParity(int[] nums) {
        /**
        分析:
        这是一个排序问题。
        解法一:按照特定的规则进行排序输出===>自定义比较器。
        解法二:还可以定义一个额外数组,先储蓄偶数,再储存奇数,最后输出整个额外数组。
        解法三:定义额外数组,使用双指针,偶数出储存左边,奇数储存右边
        解法四:原地修改,空间复杂度为o(1),使用快排分区思想
         */
         // 使用解法一
         // 使用Arrays.sort(),需要传入Object类
         Integer[] temp = new Integer[nums.length];
         for(int i = 0; i < nums.length; i++){
             temp[i] = nums[i];
         }
         // 排序后的返回类型是 void
         Arrays.sort(temp,(a,b) -> a % 2 - b % 2);
         // 将包装类的数据还给原始数组
         for(int i = 0; i < nums.length; i++){
             nums[i] = temp[i];
         }
         return nums;


    }
}

使用可快速排序分区思想

class Solution {
    public int[] sortArrayByParity(int[] nums) {
        // 使用解法四
        int p1 = 0, p2 = nums.length - 1;
        while(p1 < p2){
            if(nums[p1] % 2 > nums[p2] % 2){
                // 奇数在前则交换
                int temp = nums[p1];
                nums[p1] = nums[p2];
                nums[p2] = temp;
            }
            // 保证p1前都是偶数
            if(nums[p1] % 2 == 0){
                p1++;
            }
            // 保证p2后都是奇数
            if(nums[p2] % 2 == 1){
                p2--;
            }
        }
        return nums

    }
}

922. 按奇偶排序数组 II(两次遍历)

class Solution {
    public int[] sortArrayByParityII(int[] nums) {
        /**
        分析:
        在912题的基础上加了限制条件而已。
        遍历两次,一次安装奇数走,一次按照偶数走
         */
         int[] res = new int[nums.length];
         int i = 0; 
         for(int num:nums){
             if(num % 2 == 0){
                 // 是偶数
                 res[i] = num;
                 // 保证下标是偶数
                 i += 2;
             }
             
         }
         // 重置i为1
         i = 1;
         for(int num:nums){
             if(num % 2 == 1){
                 // 是偶数
                 res[i] = num;
                 // 保证下标是奇数
                i += 2;
                 
             }
             
         }
         // 返回最终结果
         return res;
         

    }
}

912. 排序数组(常用的排序算法)

https://leetcode-cn.com/problems/sort-an-array/solution/fu-xi-ji-chu-pai-xu-suan-fa-java-by-liweiwei1419/

169. 多数元素(摩尔投票法)

class Solution {
    public int majorityElement(int[] nums) {
        /**
        分析:
        解法一:使用哈希表,空间复杂度为O(n),时间复杂度为O(n)
        解法二:排序法:藏在管子里的蛇,身体长于管子的一半的话,砍中间就肯定能砍到,时间复杂度O(nlogn),空间复杂度o(nlogn)
        解法三:摩尔投票法,分为对抗阶段和计数阶段。如果遇到相同的,就计数,不同的就对抗。count = 0,换下一个候选人,直到遍历结束

         */
         // 哈希表
        //  int count = 0;
        //  Map<Integer,Integer> map = new HashMap<>();
        // for(int num:nums){
        //     map.put(num,map.getOrDefault(num,0) + 1);
        // }
        // for(int key:map.keySet()){
        //     if(map.get(key) > nums.length / 2){
        //         return key;
        //     }
        // }
        // return 0;


        // 排序法
        // Arrays.sort(nums);
        // return nums[nums.length / 2];

        // 摩尔投票法
        int count = 1;
        // 候选人为数组第一个元素
        int candidate = nums[0];
        // 遍历
        for(int i = 1; i < nums.length; i++){
            // 计数阶段
            if(candidate == nums[i]){
                count++;
            }else{
                // 对抗阶段
                count--;
                if(count == 0){
                    // 换候选人
                    candidate = nums[i + 1];
                }
            }
        }
        return candidate;
    }
}
  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值