数据结构算法——数组问题(以LeetCode数组题为例)

一. 数组的定义

        在程序设计中,为了处理方便,常常需要把具有相同类型的若干元素按有序的形式组织起来,这种形式就是数组(Array)。数组是程序中最常见、也是最基本的数据结构。

        对数组进行处理需要注意以下特点:

        首先,数组会利用索引来记录每个元素在数组中的位置,且在大多数编程语言中,索引是从 0 算起的。我们可以根据数组中的索引,快速访问数组中的元素。事实上,这里的索引其实就是内存地址。

        其次,作为线性表的实现方式之一,数组中的元素在内存中是连续存储的,且每个元素占用相同大小的内存。

        以LeetCode上的数组相关题目为例,学习数组问题的算法。

二. 两数求和

力扣https://leetcode.cn/problems/two-sum/

1. 题目描述

        给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 

        你可以假设每种输入只会对应一个答案。但是,你不能重复利用这个数组中同样的元素。

        你可以按任意顺序返回答案

示例:

输入: nums = [2, 7, 11, 15], target = 9

输出: [0, 1]

2. 解决方法

2.1 暴力法

        穷举所有两数之和,双重for循环

public class TwoSum1 {
    public int[] twoSum(int[] nums,int target){
        int len = nums.length;
        for(int i=0;i<len-1;i++){
            for(int j=i+1;j<len;j++){
                if(nums[i]+nums[j]==target){
                    return new int[]{i,j};
                }
            }
        }
        //如果找不到,抛出异常
        throw new IllegalArgumentException("无结果");
    }

    public static void main(String[] args) {
        int[] nums ={ 2, 7, 11, 15};
        int target = 9;
        TwoSum1 twoSum1 = new TwoSum1();
        int[] res = twoSum1.twoSum(nums, target);
        for (int re : res) {
            System.out.print(re+"\t");
        }
    }
}

复杂度分析

        时间复杂度:O(n^2)

        空间复杂度:O(1)

2.2 哈希表

        遍历数组,先将目标值与数组值相减(target-nums[i])后得到temp,判断hash表中是否存在temp,若满足条件则直接返回结果;不然将数据存入hash表,<k,v> = <元素值,索引值>。

public class TwoSum2 {
    public int[] twoSum(int[] nums,int target){
      int len = nums.length;
        HashMap<Integer,Integer> hashMap = new HashMap<>();
        for (int i = 0; i < len; i++) {
            int temp = target-nums[i];
            if(hashMap.containsKey(temp)){
                return new int[]{i,hashMap.get(temp)};
            }
            hashMap.put(nums[i],i);
        }
        //如果找不到,抛出异常
        throw new IllegalArgumentException("无结果");
    }

    public static void main(String[] args) {
        int[] nums ={ 2, 7, 11, 15};
        int target = 9;
        TwoSum2 twoSum2 = new TwoSum2();
        int[] res = twoSum2.twoSum(nums, target);
        for (int re : res) {
            System.out.print(re+"\t");
        }
    }
}

   

复杂度分析

        时间复杂度:O(n),哈希表将查找时间缩短到 O(1)

        空间复杂度:O(n),所需的额外空间取决于哈希表中存储的元素数量,该表中存储了 N 个元素。

三. 两数求和

力扣https://leetcode.cn/problems/3sum/

1. 题目描述 

        给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。

示例:

输入: nums = [-1, 0, 1, 2, -1, -4]

输出: [[-1,-1,2],[-1,0,1]]

2. 解决方法

双指针法

        双指针的思路,一般分为左右指针和快慢指针两种,以下使用左右指针。

        1. 对数组进行排序

        2. 遍历排序后的数组,进行判定:

                (1)当 nums[i] > 0 时直接break跳出,因为 nums[R] >= nums[L] >= nums[i] > 0,即 3 个数字都大于 0 ,在此固定指针 i 之后不可能再找到结果了

                  (2)  当 i > 0nums[i] == nums[i - 1]时,即遇到重复元素时,跳过此元素nums[i],因为已经将 nums[i - 1] 的所有组合加入到结果中,本次双指针搜索只会得到重复组合

        3. 定义左右指针,左右指针位于此时数组索引两端,left = i+1, right = n-1

        4. 当左右指针不重合时,计算nums[i],nums[left],nums[right]三数之和得sum,进行判定:

                   (1) 当sum=0时,将三数加入到结果集res中,且left++并跳过所有重复元素,right--并跳过所有重复元素

                   (2) 当sum>0时,right--,因为可能是num[right]过大导致

                   (3) 当sum<0时,left++,因为可能是num[left]过小导致

public class ThreeSum1 {
    public List<List<Integer>> threeSum(int[] nums) {
        int n = nums.length;
        List<List<Integer>> res = new ArrayList();
        Arrays.sort(nums);
        for(int i=0;i<n;i++){
            if(nums[i]>0)
                break;
            if(i>0 && nums[i]==nums[i-1])
                continue;
            int left = i+1, right = n-1;
            while(left < right){
                int sum = nums[i]+nums[left]+nums[right];
                if(sum == 0){
                    res.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    left++;
                    right--;
                    while(left<right && nums[left]==nums[left-1])
                        left++;
                    while(left<right && nums[right]==nums[right+1])
                        right--;
                }else if(sum > 0){
                    right--;
                }else{
                    left++;
                }
            }
        }
        return res;
    }

    public static void main(String[] args) {
        int[] nums = {-1, 0, 1, 2, -1, -4};
        ThreeSum1 threeSum1 = new ThreeSum1();
        List<List<Integer>> res = threeSum1.threeSum(nums);
        System.out.println(res);
    }
}

        

 复杂度分析:

        时间复杂度: O(N^2),其中固定指针i循环复杂度 O(N),双指针 left,right复杂度 O(N)

        空间复杂度: O(1),指针使用常数大小的额外空间

四. 下一个排列

力扣https://leetcode.cn/problems/next-permutation/

1. 题目描述

        整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更正式地,如果数组的所有排列根据其字典顺序从小到大排列在一个容器中,那么数组的 下一个排列 就是在这个有序容器中排在它后面的那个排列。如果不存在下一个更大的排列,那么这个数组必须重排为字典序最小的排列(即,其元素按升序排列)。        

        例如,arr = [1,2,3] 的下一个排列是 [1,3,2] 。
        类似地,arr = [2,3,1] 的下一个排列是 [3,1,2] 。
        而 arr = [3,2,1] 的下一个排列是 [1,2,3] ,因为 [3,2,1] 不存在一个字典序更大的排列。

        必须原地修改,只允许使用额外常数空间

示例:

输入: 1,3,8,7,6,2

输出: 1,6,2,3,7,8    

2. 解决方法

        可以观察到,若数组为全逆序,则其下一个排列为全升序。故当数组存在一个“升序子序列”时,必然可以找到它的下一个排列,所以思路如下:

        1. 寻找最后一对升序子序列

        2. 如果没找到,则说明数组全逆序,直接返回全升序

        3. 如果找到了,将升序元素(以k为下标)与其后仅小于它的元素进行交换

        4. 元素交换后,将k下标后的剩余所有元素反转为升序

public class NextPermutation {
    public void nextPermutation(int[] nums) {
        //找到右边第一对升序
        int k = nums.length - 2;
        while (k >= 0 && nums[k] >= nums[k + 1])
            k--;
        //如果全逆序,直接返回全升序
        if (k < 0) {
            Arrays.sort(nums);
            return;
        }
        //找到k之后仅大于k位置的数并交换
        int i = k + 2;
        while (i < nums.length && nums[i] > nums[k])
            i++;
        int temp = nums[k];
        nums[k] = nums[i - 1];
        nums[i - 1] = temp;

        //k以后剩余部分反转升序
        int start = k+1,end =nums.length-1;
        while (start < end){
            int reTemp = nums[start];
            nums[start] = nums [end];
            nums[end] = reTemp;
            start++;
            end--;
        }
    }

    public static void main(String[] args) {
        int[] nums = {1,3,8,7,6,2};
        NextPermutation nextPermutation = new NextPermutation();
        nextPermutation.nextPermutation(nums);
        for (int num : nums) {
            System.out.print(num+"\t");
        }
    }
}

     

复杂度分析:

        时间复杂度:O(N),其中 N 为给定序列的长度。我们至多只需要扫描两次序列,以及进行一次反转操作。

        空间复杂度:O(1),只需要常数的空间存放若干变量。

五. 旋转图像 力扣icon-default.png?t=M4ADhttps://leetcode.cn/problems/rotate-image/

1. 题目描述

        给定一个 n × n 的二维矩阵 matrix 表示一个图像。请你将图像顺时针旋转 90 度。

        你必须在 原地 旋转图像,这意味着你需要直接修改输入的二维矩阵。请不要 使用另一个矩阵来旋转图像。

示例: 

 

输入: matrix = [[5,1,9,11],[2,4,8,10],[13,3,6,7],[15,14,12,16]]

输出: [[15,13,2,5],[14,3,4,1],[12,6,8,9],[16,7,10,11]]

2. 解决方法

2.1 数学方法(先转置再翻转)

        利用矩阵的特性。所谓顺时针旋转,其实就是先转置矩阵,然后翻转每一行。

public class RotateImage1 {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        //转置矩阵
        for (int i = 0; i < n; i++)
            for (int j = i; j < n; j++) {
                int tmp = matrix[i][j];
                matrix[i][j] = matrix[j][i];
                matrix[j][i] = tmp;
            }
        //翻转行
        for( int i = 0; i < n; i++ ){
            for( int j = 0; j < n/2; j++ ){
                int tmp = matrix[i][j];
                matrix[i][j] = matrix[i][n-j-1];
                matrix[i][n-j-1] = tmp;
            }
        }

    }

    public static void main(String[] args) {
        int[][] matrix = {{5,1,9,11},{2,4,8,10},{13,3,6,7},{15,14,12,16}};
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[0].length;j++){
                System.out.print(matrix[i][j]+"\t");
            }
            System.out.println();
        }
        System.out.println("=========转换后========");
        RotateImage1 rotateImage1 = new RotateImage1();
        rotateImage1.rotate(matrix);
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[0].length;j++){
                System.out.print(matrix[i][j]+"\t");
            }
            System.out.println();
        }
    }
}

复杂度分析:

        时间复杂度:O(N^2),这个简单的方法已经能达到最优的时间复杂度O(N^2) ,因为既然是旋转,那么每个点都应该遍历到,N^2的复杂度不可避免。

        空间复杂度:O(1),旋转操作是原地完成的,只耗费常数空间。

2.2 分治法

        旋转的时候,是上下、左右分别对称的,所以我们遍历元素的时候,只要遍历一半行、一半列就可以了(1/4元素),然后直接操作矩阵元素(从外圈向内依次旋转,直到中心点或者最内圈)进行交换。

public class RotateImage2 {
    public void rotate(int[][] matrix) {
        int n = matrix.length;
        for(int i=0;i<(n+1)/2;i++){
            for(int j=0;j<n/2;j++){
                int temp = matrix[i][j];
                matrix[i][j] = matrix[n-j-1][i];
                matrix[n-j-1][i] = matrix[n-j-1][n-i-1];
                matrix[n-j-1][n-i-1] = matrix[j][n-i-1];
                matrix[j][n-i-1] = temp;
            }
        }

    }

    public static void main(String[] args) {
        int[][] matrix = {{5,1,9,11},{2,4,8,10},{13,3,6,7},{15,14,12,16}};
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[0].length;j++){
                System.out.print(matrix[i][j]+"\t");
            }
            System.out.println();
        }
        System.out.println("=========转换后========");
        RotateImage2 rotateImage2 = new RotateImage2();
        rotateImage2.rotate(matrix);
        for(int i=0;i<matrix.length;i++){
            for(int j=0;j<matrix[0].length;j++){
                System.out.print(matrix[i][j]+"\t");
            }
            System.out.println();
        }
    }
}

复杂度分析

        时间复杂度:O(N^2),是两重循环的复杂度。

        空间复杂度:O(1),我们在一次循环中的操作是“就地”完成的。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值