数组及排序LeetCode刷题记录

2、数组_排序

刷题总结:一般数组逃不过这些方法方法

  • 双指针:一个从头遍历,一个从尾遍历
  • 三指针:一个从头遍历,一个从尾遍历,一个遍历数组本身,找满足条件的进行交换
  • 从后向前遍历,从后向前填充!

75、颜色分类

方法:三指针

  • 为什么用多指针?
  1. 题目说遍历一次数组解决问题,一般都是用多指针!

三个指针各司其职:

  1. 第一个在左边确定 0 的位置
  2. 第二个在右边确定2的位置
  3. 第三个遍历数组:寻找 0 和 2 进行交换,把它与第一个指针或者第二个指针交换!
package 数组排序;


import java.util.Arrays;

/**
 * @author zhangbingbing
 * @version 1.0
 * @date 2020/4/26 9:02
 * 给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。
 *
 * 此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。
 *
 * 注意:
 * 不能使用代码库中的排序函数来解决这道题。
 *
 * 示例:
 *
 * 输入: [2,0,2,1,1,0]
 * 输出: [0,0,1,1,2,2]
 * 进阶:
 *
 * 一个直观的解决方案是使用计数排序的两趟扫描算法。
 * 首先,迭代计算出0、1 和 2 元素的个数,然后按照0、1、2的排序,重写当前数组。
 * 你能想出一个仅使用常数空间的一趟扫描算法吗?
 *
 * 来源:力扣(LeetCode)
 * 链接:https://leetcode-cn.com/problems/sort-colors
 * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */
public class _75_颜色分类 {
    /**
     分析:1、原地排序、常数空间都说明:空间复杂度:O(1)
          2、一趟扫描多数使用 双指针或者 三指针
     本题采用三指针!
     */
    public void sortColors(int[] nums) {
        int left = 0;
        int right = nums.length - 1;
        int p = 0;
        while (p < right) {
            if (nums[p] == 0) {
                swap(nums, left, p);
                left++;
                p++;
            } else if (nums[p] == 2) {
                swap(nums, right, p);
                right--;
            } else {
                p++;
            }
        }
    }
    private void swap(int[] nums, int x, int y) {
        int temp = nums[x];
        nums[x] = nums[y];
        nums[y] = temp;
    }

    public static void main(String[] args) {
        int[] nums = new int[]{2,0,2,1,1,0};
        new _75_颜色分类().sortColors(nums);
        System.out.println(Arrays.toString(nums));
    }
}

88、合并两个有序数组

  • 分析:

方法:从后向前遍历,满足条件填充

其实这一题和合并两个有序链表类似,不过这一题是 从右向左遍历,说白了就是先给大的值找到适当的位置!

因为如果找小的会被覆盖,导致值丢失,这时候换个角度岂不美哉!

  • 自己写的
给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序
 
说明:
初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。
你可以假设 nums1 有足够的空间(空间大小大于或等于 m + n)来保存 nums2 中的元素。
 
示例:
输入:
nums1 = [1,2,3,0,0,0], m = 3
nums2 = [2,5,6],       n = 3
输出: [1,2,2,3,5,6]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/merge-sorted-array
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

代码:
  		int i = m - 1;
        int j = n - 1;
        int k = m + n - 1;
        while (i >= 0 && j >= 0) {
            if (nums1[i] >= nums2[j]) {
                nums1[k--] = nums1[i--];
            } else {
                nums1[k--] = nums2[j--];
            }
        }
        while (i >= 0) {
            nums1[k--] = nums1[i--];
        }
        while (j >= 0) {
            nums1[k--] = nums2[j--];
        }
  • 最优解,相当于用归并排序优化!

我们要做的就是把第二个数组归并到第一个数组中,所以第二个数组必须扫描完,第一个退出直接将第二个接到后面!

image-20200425225241952
  • 帅气的代码!
int i = m - 1;
int j = n - 1;
int k = m + n - 1;
while (j >= 0) {
    if (i >= 0 && nums1[i] >= nums2[j]) {
        nums1[k--] = nums1[i--];
    } else {
        nums1[k--] = nums2[j--];
    }
}

面试题16.16部分排序

方法:本质上还是双指针

  1. 两次遍历一次从头开始,一次从末尾开始,找到 m 和 n
  2. 找到最大逆序对,用到了数学知识,逆序对就是 本来从小到大的,结果后面的比前面最大的小

实现:

package 数组排序;

/**
 * @author zhangbingbing
 * @version 1.0
 * @date 2020/4/26 10:22
 * 给定一个整数数组,编写一个函数,找出索引m和n,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的m和n(例如整个数组是有序的),请返回[-1,-1]。
 *
 * 示例:
 *
 * 输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
 * 输出: [3,9]
 *
 * 来源:力扣(LeetCode)
 * 链接:https://leetcode-cn.com/problems/sub-sort-lcci
 * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */
public class _面试题16_16_部分排序 {
    public int[] subSort(int[] array) {
        if (array == null || array.length == 0) {
            return new int[]{-1, -1};
        }
        int n = findN(array);
        int m = findM(array);
        return new int[]{m, n};
    }

    private int findN(int[] array) {
        //找最大逆序对的位置
        int n = -1;
        int max = array[0];
        for (int i = 1; i < array.length; i++) {
            if (array[i] >= max) {
                max = array[i];
            } else {
                n = i;
            }
        }
        return n;
    }
    private int findM(int[] array) {
        //找最大逆序对的位置
        int m = -1;
        int min = array[array.length - 1];
        for (int i = array.length - 1; i >= 0; i--) {
            if (array[i] <= min) {
                min = array[i];
            } else {
                m = i;
            }
        }
        return m;
    }
}

复杂度:O(n)

可以优化成一次遍历 : 不过复杂度一样,因为for循环中执行的总代码数一样

if (array == null || array.length == 0) {
    return new int[]{-1, -1};
}
//优化成遍历一次
int m = -1, n = -1;
int len = array.length;
int max = array[0],min = array[len - 1];
for (int i = 1; i < len; i++) {
    if (array[i] >= max) {
        max = array[i];
    } else {
        n = i;
    }
    if (array[len - i - 1] <= min) {
        min = array[len - i - 1];
    } else {
        m = len - i - 1;
    }
}
return new int[]{m, n};

很强,不过要注意边界问题!要保证两边都得遍历完

977、有序数组的平方

方法:

又是双指针,所以说,数组逃不掉指针!

package 数组排序;

import java.util.Arrays;

/**
 * @author zhangbingbing
 * @version 1.0
 * @date 2020/4/26 11:48
 * 给定一个按非递减顺序排序的整数数组 A,返回每个数字的平方组成的新数组,要求也按非递减顺序排序。
 *
 *  
 *
 * 示例 1:
 *
 * 输入:[-4,-1,0,3,10]
 * 输出:[0,1,9,16,100]
 * 示例 2:
 *
 * 输入:[-7,-3,2,3,11]
 * 输出:[4,9,9,49,121]
 *
 * 来源:力扣(LeetCode)
 * 链接:https://leetcode-cn.com/problems/squares-of-a-sorted-array
 * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */
public class _977_有序数组的平法 {
    public int[] sortedSquares(int[] A) {

/*        //1、low的方法
        for (int i = 0; i < A.length; i++) {
            A[i] = A[i]*A[i];
        }
        Arrays.sort(A);
        return A;
        */

    //1、本来的方法
        int len = A.length;
        int[] ans = new int[len];
        int r = 0, j = len - 1, cur = j;
        while (j > r) {
            ans[cur--] = (A[j]*A[j] >= A[r]*A[r]) ? (A[j]*A[j--]) : (A[r]*A[r++]);
        }
        return ans;
    }

    public static void main(String[] args) {
        int[] arr = new int[]{-4,-1,0,3,10};
        int[] ints = new _977_有序数组的平法().sortedSquares(arr);
        System.out.println(Arrays.toString(ints));
    }
}

数组还有个喜欢考的,就是二分查找,就是要求数组必须有序

但是这一题做到了无序问题,我不知道意义何在?

package 数组排序;

/**
 * @author zhangbingbing
 * @version 1.0
 * @date 2020/4/27 15:31
 * 假设按照升序排序的数组在预先未知的某个点上进行了旋转。
 *
 * ( 例如,数组 [0,1,2,4,5,6,7] 可能变为 [4,5,6,7,0,1,2] )。
 *
 * 搜索一个给定的目标值,如果数组中存在这个目标值,则返回它的索引,否则返回 -1 。
 *
 * 你可以假设数组中不存在重复的元素。
 *
 * 你的算法时间复杂度必须是 O(log n) 级别。
 *
 * 示例 1:
 *
 * 输入: nums = [4,5,6,7,0,1,2], target = 0
 * 输出: 4
 * 示例 2:
 *
 * 输入: nums = [4,5,6,7,0,1,2], target = 3
 * 输出: -1
 *
 * 来源:力扣(LeetCode)
 * 链接:https://leetcode-cn.com/problems/search-in-rotated-sorted-array
 * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */
public class _33_搜索旋转排序链表 {
    public int search(int[] nums, int target) {
        if (nums == null || nums.length == 0) return -1;
        int begin = 0;
        int end = nums.length - 1;
        while (begin <= end) {
            int mid = (begin + end) >> 1;
            int res = nums[mid];
            if (target == res) return mid;
            if (nums[0] <= res) {
                //说明左边有序,在左边找
                if (target < res && target >= nums[0]) {
                    end = mid - 1;
                } else {
                    begin = mid + 1;
                }
            } else {
                //右边有序
                if (target > res && target <= nums[nums.length - 1]) {
                    begin = mid + 1;
                } else {
                    end = mid - 1;
                }
            }
        }
        return -1;
    }

    public static void main(String[] args) {
        System.out.println(new _33_搜索旋转排序链表().search(new int[]{4,5,6,7,0,1,2}, 3));
    }
}

560、和为K的子数组数

没用到数组的经典算法,算是简单的题

  • 按照我的思路,题解写在代码上
package 数组排序;

import java.util.HashMap;
import java.util.Map;

/**
 * @author zhangbingbing
 * @version 1.0
 * @date 2020/5/15 10:19
 * 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。
 * <p>
 * 示例 1 :
 * <p>
 * 输入:nums = [1,1,1], k = 2
 * 输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
 * <p>
 * 来源:力扣(LeetCode)
 * 链接:https://leetcode-cn.com/problems/subarray-sum-equals-k
 * 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
 */
public class _560_和为K的子数组数 {

    //枚举算法的想法也是遍历的时候想到每一步要干嘛,在进行暴力枚举!
    public int subarraySum(int[] nums, int k) {
        if (nums == null) return 0;
        int count = 0;
        //枚举枚举肯定要遍历关键是每次遍历我们要干什么:**求出它的前面的所有子数组和**
        for (int i = 0; i < nums.length; i++) {
            int sum = 0;
            //明确了目的 暴力枚举就简单了
            //这个循环干的就是那个目的
            for (int j = i; j >= 0; j--) {
                sum += nums[j];
                if (sum == k) {
                    count++;
                }
            }
        }
        return count;
    }


    //优化成哈希表,难,像是动态规划的递推
    public int subarraySum2(int[] nums, int k) {
        int count = 0, sum = 0;
        //我们用map来存储
        Map<Integer, Integer> map = new HashMap<>();
        map.put(0,1);
        for (int i = 0; i < nums.length; i++) {
            //用sum来存储前i项的和,把每一个和都存储在map中
            sum += nums[i];
            if (map.containsKey(sum - k)) {     //如果两个和相差恰好是k,则肯定有一串连续子数组之和是这个
                count += map.get(sum - k);
            }
            map.put(sum, map.getOrDefault(sum, 0) + 1);
        }
        return count;
    }


    public static void main(String[] args) {
        System.out.println(new _560_和为K的子数组数().subarraySum(new int[]{1, 2, 3, 4, 2, 1, 7}, 3));
    }
}

都是用心的总结,望点赞,评论讨论!!

posted @ 2020-05-15 22:45  贝加尔湖畔╭  阅读(228)  评论(0编辑  收藏  举报