剑指 Offer 21. 调整数组顺序使奇数位于偶数前面

输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分。
示例:
输入:nums = [1,2,3,4]
输出:[1,3,2,4]
注:[3,1,2,4] 也是正确的答案之一。

myself题解思路:双指针的思想:左右寻找偶数和奇数然后进行交换,特殊的一点在于有可能数组是全奇或者全偶,所以需要在while循环中加入x< y的判定。

  1. class Solution {
  2. public int[] exchange(int[] nums) {
  3. int x = 0;
  4. int y = nums.length - 1;
  5. while (x < y) {
  6. while (x < y && nums[x] % 2 != 0) {
  7. x++;
  8. }
  9. while (x < y && nums[y] % 2 == 0) {
  10. y--;
  11. }
  12. if (x < y) {
  13. swap(x, y, nums);
  14. }
  15. }
  16. return nums;
  17. }
  18. public void swap(int x, int y, int[] nums) {
  19. int tmp = nums[x];
  20. nums[x] = nums[y];
  21. nums[y] = tmp;
  22. }
  23. }

总结:首尾双指针 定义头指针 left ,尾指针 right . left 一直往右移,直到它指向的值为偶数 right 一直往左移, 直到它指向的值为奇数 交换 nums[left] 和 nums[right]. 重复上述操作,直到 left == right .

注意:if(x<y)这一步判断条件

题解思路:快慢双指针

  • 定义快慢双指针fast和low,fast在前,low在后。
  • fast的作用是向前搜索奇数位置,low的作用是指向下一奇数应当存放的位置
  • fast向前移动,将它搜索到奇数时,将它和nums[low]交换,此时low向前移动一个位置。
  • 重复上述操作,直到fast指向数组末尾。

    class Solution {
      public int[] exchange(int[] nums) {
          int slow = 0;
          int fast = 0;
          while (fast < nums.length) {
              if (nums[fast] % 2 != 0) {        //(nums[fast] & 1) != 0
                  swap(slow, fast, nums);
                  slow++;
              }
              fast++;
          }
          return nums;
      }
    
      public void swap(int x, int y, int[] nums) {
          int tem = nums[x];
          nums[x] = nums[y];
          nums[y] = tem;
      }
    }
    

    总结:可以通过nums[fast] % 2 != 0判断奇数还是偶数

剑指 Offer 45. 把数组排成最小的数

输入一个非负整数数组,把数组里所有数字拼接起来排成一个数,打印能拼接出的所有数字中最小的一个。
示例 1:
输入: [10,2]
输出:102"

解题思路:

  • 此题求拼接的“最小数字”,本质上是一个排序问题。。
  • 排序判断规则:设nums任意两个数字的字符串格式x和y,则
    • 若拼接的字符串x+y>y+x,则m>n;
    • 反之,若x+y<y+x,则n<m;
  • 根据以上规则,套用任何排序方法对nums执行排序即可。

    class Solution {
      public String minNumber(int[] nums) {
          if (nums == null || nums.length == 0) {
              return "";
          }
          int n = nums.length;
          String[] tem = new String[n];
          for (int i = 0; i < n; i++)
              tem[i] = nums[i] + "";
          Arrays.sort(tem, (s1,s2) -> (s1+s2).compareTo(s2 + s1));
          StringBuilder res = new StringBuilder();
          for (String s : tem) {
              res.append(s);
          }
          return res.toString();
      }
    

    总结:Arrays.sort(tem,(s1,s2)->(s1+s2).compareTo(s2+s1)),匿名表达式。

  • 题解方法2:快排思想

    class Solution {
      public String minNumber(int[] nums) {
          String[] strs = new String[nums.length];
          for(int i = 0; i < nums.length; i++)
              strs[i] = String.valueOf(nums[i]);
          fastSort(strs, 0, strs.length - 1);
          StringBuilder res = new StringBuilder();
          for(String s : strs)
              res.append(s);
          return res.toString();
      }
      void fastSort(String[] strs, int l, int r) {
          if(l >= r) return;
          int i = l, j = r;
          String tmp = strs[i];
          while(i < j) {
              while((strs[j] + strs[l]).compareTo(strs[l] + strs[j]) >= 0 && i < j) j--;
              while((strs[i] + strs[l]).compareTo(strs[l] + strs[i]) <= 0 && i < j) i++;
              tmp = strs[i];
              strs[i] = strs[j];
              strs[j] = tmp;
          }
          strs[i] = strs[l];
          strs[l] = tmp;
          fastSort(strs, l, i - 1);
          fastSort(strs, i + 1, r);
      }
    }
    

剑指 Offer 51. 数组中的逆序对

在数组中的两个数字,如果前面一个数字大于后面的数字,则这两个数字组成一个逆序对。输入一个数组,求出这个数组中的逆序对的总数。
示例 1:
输入: [7,5,6,4]
输出: 5

解题思路:

直观来看,使用暴力统计法即可,即遍历数组的所有数字对并统计逆序对数量,此方法时间复杂度为O(N2),观察题目给定的数字长度范围0<N<50000,可知此复杂度是不能接受的。

[归并排序」与「逆序对」是息息相关的。归并排序体现了 “分而治之” 的算法思想,具体为:

  • 分: 不断将数组从中点位置划分开(即二分法),将整个数组的排序问题转化为子数组的排序问题;
  • 治: 划分到子数组长度为 1 时,开始向上合并,不断将较短排序数组合并为较长排序数组,直至合并至原数组时完成排序;

    如下图所示,为数组[7,3,2,6,0,1,5,4]的归并排序过程

排序 - 图1

算法流程

merge_sort()归并排序与逆序对统计:
1、终止条件:当l>=r是,代表子数组长度为1,此时终止划分;
2、递归划分:计算数组中点m,递归划分左子数组merge_sort(l,m)和右子数组merge_sort(m+1,l);
3、合并与逆序对统计:
1、暂存数组nums闭区间【i,r】内的元素至辅助数组tmp;
2、循环合并: 设置双指针 ii , jj 分别指向左 / 右子数组的首元素;
当 i = m + 1 时: 代表左子数组已合并完,因此添加右子数组当前元素 tmp[j] ,并执行 j = j + 1;
否则,当 j = r + 1 时: 代表右子数组已合并完,因此添加左子数组当前元素 tmp[i] ,并执行 i = i + 1 ;
否则,当 tmp[i]≤tmp[j] 时: 添加左子数组当前元素 tmp[i] ,并执行 i = i + 1;
否则(即 tmp[i] > tmp[j])时: 添加右子数组当前元素 tmp[j] ,并执行 j = j + 1 ;此时构成 m - i + 1 个「逆序对」,统计添加至 res ;
4、返回值: 返回直至目前的逆序对总数 res ;

reversePairs() 主函数:
1、初始化:辅助数组tmp,用于合并阶段暂存元素;
2、返回值:执行归并排序merge_sort( ),并返回逆序对总数即可。
排序 - 图2

在标准归并排序模板上进行操作

class Solution {
    int count;
    public int reversePairs(int[] nums) {
        int left=0,right=nums.length-1;
        merge(nums,left,right);
        return count;
    }

    public void merge(int[] nums,int left,int right){
        if(left>=right)
            return;
        int mid=(right+left)/2;
        merge(nums,left,mid);
        merge(nums,mid+1,right);
        mergeSort(nums,left,mid,right);
    }

    public void mergeSort(int[] nums,int left,int mid,int right){
        int[] temparr=new int[right-left+1];
        int index=0;
        int temp1=left,temp2=mid+1;

        while(temp1<=mid&&temp2<=right){
            if(nums[temp1]<=nums[temp2])
                temparr[index++]=nums[temp1++];
            else{
                count+=(mid-temp1+1);
                temparr[index++]=nums[temp2++];
            }
        }

        while(temp1<=mid){
            temparr[index++]=nums[temp1++];
        }

        while(temp2<=right)
            temparr[index++]=nums[temp2++];

        for(int i=0;i<temparr.length;i++){
            nums[i+left]=temparr[i];
        }
    }
}