算法记录-坚持(排序相关)

剑指offer45.把数组排成最小的数

此题用的是改进版的冒泡排序方法~

class Solution {
    public String minNumber(int[] nums) {
        bubbleSort(nums);
        return Arrays.toString(nums).replace("[", "").replace("]", "").replace(",", "").replace(" ","");
    }

    public static void bubbleSort(int[] nums) {
        boolean swapped = true;
        int indexOfUnsortedNums = nums.length - 1;  //未被交换的下标(其后都已排序)
        int swapedIndex = -1; //最后一次交换的下标
        while(swapped) {
            swapped = false;
            for(int i = 0; i < indexOfUnsortedNums; i++) {
                if((""+nums[i]+nums[i+1]).compareTo(""+nums[i+1]+nums[i]) > 0) {
                    swap(nums,i,i+1);
                    swapped = true;
                    swapedIndex = i;
                }
            } indexOfUnsortedNums = swapedIndex; 
        }
    }

    public static void swap(int[] nums, int i, int j) {
        //通过i,j交换nums里的值,不然你传个nums干嘛!
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }
}

283.移动零

指路👈
还是用的冒泡法,对于是0的数,冒泡到最后一个位置。

class Solution {
    public void moveZeroes(int[] nums) {
        int zeroNums = 0;
        for(int i = 0; i < nums.length - zeroNums; i++) {
            if(nums[i] == 0) {
                //把零冒泡到最后
                zeroNums++;
                for(int j = i; j < nums.length - zeroNums; j++){
                   sort(nums,j,j+1);
                }
                 // 下一轮遍历时 i 会增加 1,但此时 nums[i] 已经和 nums[i+1] 交换了,nums[i+1] 还没有判断是否为 0,所以这里先减 1,以使下一轮继续判断 i 位置。
                i--;
            }
        }
    }

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

数组第k个最大值

数组第k个最大值。这题可以用选择排序,最外层循环k次即可

class Solution {
    public int findKthLargest(int[] nums, int k) {
        int maxIndex = 0;
        for(int i = 0; i < k; i++){
            maxIndex = i;
          //  for(int j = 0; j < nums.length - i; j++) {
              for(int j = i + 1; j < nums.length; j++) {//因为k可能就是数组长度~
                if(nums[maxIndex] < nums[j]) {
                    maxIndex = j;
                }
            } 
              swap(nums,maxIndex,i);//交换k次
        }
        return nums[k-1];
    }

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

对链表进行插入排序

插入排序去操作的链表,要考虑建立什么辅助结点,其次要理解插入排序的概念。其实用到的思路就是:“我如果比你小,我就自动自觉去到前面自助找好座位。我要是比你大,那么继续往下比较的权力就落在我手上。”

class Solution {
    public ListNode insertionSortList(ListNode head) {
        if(head == null) return null;
        //建立一个哑结点,方便处理
        ListNode dummyHead = new ListNode(0);
        dummyHead.next = head;
        //记录已排序完成的结点
        ListNode lastSorted = head;
        //记录目前要和lastSorted结点比较的结点
        ListNode current = head.next;

  //     while(current.next == null) {
      while(current != null) {
          if(lastSorted.val <= current.val) {
              lastSorted = lastSorted.next;
          } else {
              //从投开始查找适合他的位置,所以要用while,且不是previousHelper.val
              ListNode previousHelper = dummyHead;
              while(current.val >= previousHelper.next.val) {
                  previousHelper = previousHelper.next;
              } 
              //开始插入
              lastSorted.next = current.next;
              current.next = previousHelper.next;
              previousHelper.next = current;
          }
          //更新需比较的结点
          current = lastSorted.next;
      }
        return dummyHead.next;
    }
}

快速排序,想到上学期的数据结构的课,画了好多快排的题但是写起代码来,真的耗费时,因为不太熟悉代码的编写~

class Solution {
    public int[] sortArray(int[] nums) {
        quikSort(nums,0,nums.length - 1);
        return nums;
    }

    //快速排序的基本框架(递归实现)
    public static void quikSort(int[] arr, int startIndex, int endIndex) {
        //递归退出的判断条件
        if(startIndex >= endIndex) {  
            System.out.println(arr[0]);
            return;
        }
        //拿到分区后的锚的所在位置
        int pivotIndex = partitiion(arr,startIndex,endIndex);//参数传递的是上面传过来的参数哇!
        //上面分区完的左边区间的分区,不是调用分区函数啦!!!
      //  partitiion(arr,0,pivotIndex);这也是错的,分区范围不可能一直从0开始
     // partitiion(arr,startIndex,pivotIndex);
      quikSort(arr,startIndex,pivotIndex-1);
      //这是右边
      //quikSort(arr,pivotIndex+1,arr.length-1);这是错的,分区范围是变动的,不能一直是arr.length-1
        quikSort(arr,pivotIndex+1,endIndex);
    } 

    //分大小的具体实现
    private static int partitiion(int[] arr, int startIndex, int endIndex) {
        //取一个锚
     //   int pivot = startIndex;错啦,应该是数组对应的值,而不是下标!
        int pivot = arr[startIndex];
        int left = startIndex;
        int right = endIndex;

        //开始根据pivot分类
     //   while(left < right) {这个要放在里面判断
         while(left != right) {
            //控制right指针,至到遇到比pivot小的值
            while(arr[right] > pivot && right > left) {right--;}
             //控制left指针,至到遇到比pivot大的值(这里是小于等于呢)
            while(arr[left] <= pivot && right > left) {left++;}
            //如果还没相遇就停了,说明需要交换
             if(left < right) {
                swap(arr,left,right);
            }
        }
        //pivot与两个指针重合点交换位置
        swap(arr,startIndex,left);
        //返回的是pivot交换后的坐标,就是left
        return left;
    }

    //交换方法
    /*private static void swap(int[] arr, int p, int q) {
      /*  int temp = arr[p];
        arr[q] = temp;
        arr[p] = arr[q];*/
        //闹笑话了,交换函数写错了
    

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

169-多数元素

3种方法写多数元素包括摩尔计数法

class Solution {
    public int majorityElement(int[] nums) {
        Arrays.sort(nums);
        return nums[nums.length >> 1];
    }
}

合并排序数组

leetcode 合并排序数组
方法一,直接合并后排序。方法二,双指针方法,需要额外建立一个数组存放元素。方法三,逆向双指针。

class Solution {
    public void merge(int[] A, int m, int[] B, int n) {
        int pa = m -1; int pb = n - 1;  //从后开始遍历
        int tail = m+n-1;
        int cur = 0; 
        while(pa>=0 || pb >= 0) {
            if(pa==-1){
                cur = B[pb--];
            } else if(pb==-1){
                cur = A[pa--];
            } else if(A[pa]>B[pb]) {
                cur = A[pa--];
            } else {
                cur = B[pb--];
            }
            A[tail--] = cur;
        }
    }
}

数组中的逆序对

leetcode 数组中的逆序对
这道题我学归并算法的时候已经看过一遍解析了,结果还是不会!这道题的思路是看前面有多少个比自己大的个数。那么用归并的原因是,归并具有很明显的阶段性排序。归并就是把原数组分为两组,分步用两个指针指向第一位元素。如果是从小到大的顺序排,看看哪个指针指向的数更小,就先合并。也就是我找到小的数了,那我待排序数组里还没有放入排序数组的元素不都是比我大的吗!!当然前提是两个子区间分别有序。
其次,归并的过程也是分治思想,用先调用后执行的递归来实现整个归并排序。在排序中多了一步计算逆序对的步骤。(心中要有那个图!)

class Solution {
    public int reversePairs(int[] nums) {
        int len = nums.length;
        if(len < 2) {
            return 0;  //元素数组只要一个时,逆序对就是1
        }
        int[] copy = new int[len];  //待排序的数组
        for(int i = 0;i<len;i++){
            copy[i] = nums[i];
        }
        int[] tmp = new int[len];  //分治归并过程中的工具过程数组
        return reversePairs(copy,0,len-1,tmp);  //方法的重载,返回逆序对的个数
    }

    private int reversePairs(int[] nums,int left,int right,int[] tmp){
        if(left == right) {
            return 0;  //只有一个元素时就可以退出递归了
        }

        int mid = left+(right-left)/2;
        //左边区间的逆序对数
        int leftPairs = reversePairs(nums,left,mid,tmp);
         //右边区间的逆序对数
        int rightPairs = reversePairs(nums,mid+1,right,tmp);
        
        //如果左边最大的比右边最小的还要小,那就是直接合并的了,合并过程产生的逆序对就是0
        //因为分治合并过程,左右区间都是有序的
        if(nums[mid] <= nums[mid+1]) {  //是小于等于噢
            return leftPairs + rightPairs;
        }
        
        //合并过程产生的逆序对
        int crossPairs = mergeAndCount(nums,left,mid,right,tmp);

        return crossPairs+leftPairs+rightPairs; //总的逆序对
    }

    private int mergeAndCount(int[] nums, int left, int mid, int right, int[] tmp){  //分治法归并过程
        for(int i = left; i <= right; i++){  //也是小于等于哟,不要把最后一个落下了
            tmp[i]=nums[i];
        }

        int i = left; //左起始边界
        int j = mid+1; //右起始边界
        int count = 0;//计算逆序对
        for(int k = left; k <= right; k++) {  //必须大于等于,不然就是不稳定的归并排序
            if(i==mid+1) { //i用完了,只能把右边的元素归并了
                nums[k]=tmp[j];
                j++;
            } else if(j==right+1){ //j用完了,只能把左边的元素归并了
                nums[k]=tmp[i];
                i++;
            } else if(tmp[i] <= tmp[j]) {  //也是小于等于哟不然就有大家都是相等的情况就没有人管
                //把左边小的归并了
                nums[k]=tmp[i];
                i++;
            } else {
                //把右边的归并了,并且要计算左边还剩多少个元素-->逆序对
                nums[k]=tmp[j];
                j++;
                count+=(mid-i+1); //计算逆序对
            }
        }
        return count;  
    }
}

数组的相对排序

一个计数排序的例子。

class Solution {
    public int[] relativeSortArray(int[] arr1, int[] arr2) {
        int upper = 0; //arr2中的最大值
        for(int num : arr1) {
            upper = Math.max(upper,num);  //获取arr2的最小值
        }
        int[] frequency = new int[upper+1]; //记得加1,不然值和下标就对应不上了
        //往frequency里添加数据
        for(int num: arr1) {
            ++frequency[num];
        }
        //接着开始根据frequency计数排序了,先排arr2里出现的(搞清楚arr1和arr2的关系了喂!)
        int[] result = new int[arr1.length];
        int index = 0;
        for(int num:arr2) {,
            for(int i = 0; i < frequency[num]; ++i){  //Java中i++语句是需要一个临时变量取存储返回自增前的值,而++i不需要,但是两者的结果一样,++i更加优
                result[index++] = num;
            }
            frequency[num] = 0;  //清零
        }
        //接着排未在arr1里出现的数字
        for(int j = 0; j <= upper; ++j) {
            for(int i = 0;i < frequency[j];++i){
                result[index++] = j;
            }
        }
        return result;
    }
}

标题164. 最大间距

大佬解析,超容易理解
方法一:为了满足现线的时间复杂度,对整个数组采用基数排序后再进行相减比较即可。

class Solution {
    public int maximumGap(int[] nums) {
        if(nums.length <= 1){
            return 0;
        }
        //使用10个Arraylist装载每次基数排序的过程
        List<ArrayList<Integer>> lists = new ArrayList<>();
        for(int i = 0; i < 10; i++){
            lists.add(new ArrayList<>());
        }
        int exp = 1;  //先除1再除10取余,第二次除10再除10取余,第二次除100再除10取余......
        int maxNum = 0; //要以最大值是否不能再取余作为基数排序进行完否的判断条件
        for(int i = 0; i < nums.length; i++) {
            if(maxNum < nums[i]) {
                maxNum = nums[i];
            }
        }

        //开始基数排序
        while(maxNum > 0) {
            //1.清空数据
              for(int i = 0; i < 10; i++) {
                 lists.set(i,new ArrayList<Integer>());
              }
            //2.装载数据
             for(int i = 0; i < nums.length; i++) {
               int tmp = (nums[i]/exp)%10;
               lists.get(tmp).add(nums[i]);
            }
            //3.重新拿出来
            int index = 0; //数组下标
             for(int i = 0; i < 10; i++) {
                 for(int j = 0; j < lists.get(i).size(); j++){
                    nums[index++] = lists.get(i).get(j);
                 }
              }
            exp *= 10;
            maxNum /= 10;
        }
        int maxGap = 0;
        //对已经排好序的数组依次计算最大间距
         for(int i = 0; i < nums.length - 1; i++) {
            maxGap = nums[i+1] - nums[i] > maxGap ? nums[i+1] - nums[i] : maxGap;    
        }
        return maxGap;
    }
}

使用桶排序的思路,考虑的问题就是建立多少个桶?一般用除去最大值和最小值的元素个数total-2+1个桶就行。最大最小值前或者后面没有桶。只要比total-2大就可以了明白桶里存在maxGap,桶间也可以存在gap。接着算桶内interval。
interval是桶最多能放的个数,max和min是给的数字里的最大最小值。(nums[i]-min)/interval计算得到数字应该放在哪个桶,同时要保证最后有一个桶是空的。

561.数组拆分Ⅰ

数组拆分Ⅰ
这题没有诈!直接排序即可。

class Solution {
    public int arrayPairSum(int[] nums) {
        Arrays.sort(nums);
        int res = 0;
        for(int i = 0; i < nums.length; i+=2) {
            res += nums[i];
        }
        return res;
    }
}

75.颜色分类

颜色分类
首先,这道题直接排序就可以了,问题是不用库函数,又要在原函数进行,好家伙我一开始都没想到用什么!指针啊!!单指针两次循环,双指针一次循环。

class Solution {
    public void sortColors(int[] nums) {
        int p0=0,p1=0;//控制0和1的两个指针
        
        for(int i=0;i<nums.length;i++){
            if(nums[i]==1) {
                int tmp = nums[p1];
                nums[p1] = 1;
                nums[i] = tmp;
                p1++; 
            } else if (nums[i]==0) {
                int tmp = nums[p0];
                nums[p0] = 0;
                nums[i] = tmp;
                if(p0<p1) {  //保证被0换出去的1能正确地摆回1的后面
                     int tmp2 = nums[p1];
                     nums[p1] = 1;
                     nums[i] = tmp2;
                }
                p0++;
                p1++;
            } 
        }
    }
}

以上是双指针的,单指针记得从0后开始就行了。

第一个只出现一次的字符

这道题的思路就是用哈希表去记录每个字符出现的频率。

class Solution {
    public char firstUniqChar(String s) {
        Map<Character,Integer> freqencies = new HashMap<Character,Integer>();
        for(int i=0; i<s.length(); i++) { 
            char ch = s.charAt(i);//charAt() 方法用于返回指定索引处的字符。索引范围为从 0 到 length() - 1。
            freqencies.put(ch,freqencies.getOrDefault(ch,0)+1); //getOrDefault()方法更快捷获取Map中的值,是get的功能升级版
        }
        for(int i=0; i<s.length(); i++) { 
            if(freqencies.get(s.charAt(i))==1) {
                return s.charAt(i);
            }
        }
        return ' ';
    }
}

使用LinkedHashMap的方法吧,其实和方法一差不多,相当于介绍一下这种数据结构。

因为LinkedHashMap可以存储放入元素的顺序,所以先遍历一次字符串,获取到对应的字符的频率;接着遍历map,发现第一个key对应的value是1,则返回该字符。

public char firstUniqChar(String s) {
        LinkedHashMap<Character, Integer> map = new LinkedHashMap<>();
        for (int i = 0; i < s.length(); i++) {
            char c = s.charAt(i);
            map.put(c, map.getOrDefault(c, 0) + 1);
        }
        for (Character c : map.keySet()) {
            if (map.get(c) == 1){
                return c;
            }
        }
        return ' ';
    }

类似地,下面出一题数字相关的。

只出现一次的数字

同样可以用哈希结构记录出现的频率,掌握好哈希表的遍历。这里需要用到HashMap的键和值。HashMap以键值对存储数据,其中Key-Value都是Map.Entry中的属性,故使用了以下的遍历方式。

class Solution {
    public int singleNumber(int[] nums) {
        Map<Integer, Integer> freq = new HashMap<Integer, Integer>();
        for (int num : nums) {
            freq.put(num, freq.getOrDefault(num, 0) + 1);
        }
        int ans = 0;
        for (Map.Entry<Integer, Integer> entry : freq.entrySet()) {
            int num = entry.getKey(), occ = entry.getValue();
            if (occ == 1) {
                ans = num;
                break;
            }
        }
        return ans;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值