刷穿剑指offer-Day05-数组I

前文回顾

之前我们使用三天的时间,学习了整数章节的知识学习。并在Day4的总结中,结合读者们关于算法学习和刷题过程中的疑问,进行了相关解答。

当然朋友们也提了一些关于文章整体讲解上的建议,总体来说:

  1. 每章的第一篇文章会将本章知识点全部讲解,但是当天的题目并不涉及这些内容,等后面的题目用到该知识点的时候,还要返回去看本章的第一篇文章,比较麻烦。
  2. 算法题目讲解的时候,当前多为文字描述,希望能多添加一些配图帮助理解。
  3. 当前剑指offer学习群有些冷清,能组织一些刷题活动,类似交作业的方式会更好点。

谢谢大家提出的建议,也期待大家的持续反馈。

对于第一条,考虑之后将每章的知识点进行拆分,每天根据题目先讲解针对性的知识点后,再进行题目讲解。

第二条关于配图的这个,算是一条不足了,之后会配上图片或者动图,更为细致的讲解题目

最后一条嘛,就不能太强求了,毕竟很多朋友周内比较忙,可能更多的是看看题解,周末的时候再集中刷题。当然为了满足大家,之后考虑每天会留一道题目当做课后作业,然后第二天在发布当天内容的同时,讲解前一天的作业题目。内容可能是当前章节,也可能是之前某章题目的回顾。这道题目的讲解,放在文末或者单独开一篇文章。

浅析数据结构

OK,今天开始,我们要走进算法的第一个数据结构-----数组了。但在学习之前,我们首先应该对数据结构进行一个了解。

何为数据结构: 数据结构是计算机存储、组织数据的方式。

数据结构大致可划分为三类:线性结构、树形结构、图形结构。(画个丑图,大家理解下):

其中他们各自,又细化出了更多子结构,比如:

  • 线性结构*(线性表)

    • 数组
    • 链表
    • 队列
    • 哈希表(散列表)

    ps: 哈希表是一种特殊的线性表,采用了哈希算法。同时有链表和线性表的优点,但占的空间大,牺牲空间换取了效率。

  • 树形结构

    • 二叉树(完全二叉树、满二叉树、平衡二叉树)
    • Trie(字典树)
    • B树
    • 红黑树
    • 哈夫曼树
    • 并查集
  • 图形结构

    • 邻接矩阵
    • 临街表

理解了这几种数据结构的分类,就可以开始本章的正式内容了。(话说前戏有点太多了....)

数组(Array)

根据上面的分类,我们了解到数组属于线性表的一种特殊场景。线性表是具有n个相同类型元素的序列。而数组则是一种顺序存储的线性表,其中所有元素的内存地址是连续的。

而在很多编程语言中,数组都有一个致命的缺点,一旦初始化后,则无法动态的去修改容量

那么很多Python的小伙伴就说了,python的List和上面说的内容大相径庭啊!list不仅可以存储不同数据类型的元素,还支持动态扩容,完全不是一码事儿!

所以我专门把很多两个字加粗了。Python中list是采用分离式技术实现的动态顺序表。

  • “分离式技术”,指的是表头信息和数据地址是分开的。
  • "动态"指的是元素存储区可以更换,用以实现元素的增删。

在算法学习的初期,小伙伴们可以使用Python来刷题,因为Python的很多内置库与底层优化,可以方便我们写出更为简便的代码。但掌握了一定基础后,还是建议大家挑选一门更为底层的语言Java或者C++来进行二刷,以便了解更多Python中被开发者优化过的内容。至于C就算了,已经底层的有点入矿井了...

虽然Python有无脑list,但在这里还是希望Python的朋友们,也能继续往下来看看Java关于数组的一些知识,考虑下如果Python的List和Java的Array一样,我们该如何做题.

数组的初始化

在Python中,list由于为动态数组,所以初始化可以很随意:

  • li = []
  • li = list()
  • li = [1, 2, 3, 4, 5]

但在Java中,由于数组初始化后,长度就无法再修改了,所以分为了静态初始化和动态初始化两种:

  • 静态初始化: int[] arr = {1, 2, 3, 4, 5};
  • 动态初始化: int[] arr1 = new int[5]; //数组通过int初始化后,每个元素默认为0。

数组的遍历

Python和Java在数据的遍历,都可以分为两种:

  1. 通过获取数组长度,然后控制下标变更来访问数组的元素
  2. 直接遍历每个元素

下标访问

python:

li = [1, 2, 3, 4, 5]
for i in range(len(li)):
    print(li[i])

Java:

int[] arr = {1, 2, 3, 4, 5};
for (int i = 0; i < arr.length; i++) {
    System.out.println(arr[i]);
}

遍历每个元素

Python:

for i in li:
    print(i)

# 特殊的 enumerate操作,方便同时获取下标与内容
for index, val in enumerate(li, start=0):
    print(f"index:{index},value:{val}")

Java (for each):

int[] arr = {1, 2, 3, 4, 5};
for (int i:arr) {
    System.out.println(i);
}

数组操作

打印数组及数组内存地址

Python:

li = [1, 2, 3, 4, 5]
# 打印数组内存地址
print(id(li))  # 2020779057800
# 打印数组
print(li)      # [1, 2, 3, 4, 5]

Java:

import java.util.Arrays;

int[] arr = {1, 3, 2, 4, 5};
// 打印内存地址
System.out.println(arr); // [I@1b6d3586
// 打印数组
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5]
Arrays.sort(arr);

数组排序

Python:

# li.sort()原地修改
li = [1, 3, 2, 4, 5]
print(li) # [1, 3, 2, 4, 5]
li.sort()
print(li) # [1, 2, 3, 4, 5]

# sorted(li)创建新数组并返回
li = [1, 3, 2, 4, 5]
new_li = sorted(li)
print(new_li) # [1, 2, 3, 4, 5]

Java:

import java.util.Arrays;

int[] arr = {1, 3, 2, 4, 5};
System.out.println(Arrays.toString(arr)); // [1, 3, 2, 4, 5]
Arrays.sort(arr);
System.out.println(Arrays.toString(arr)); // [1, 2, 3, 4, 5]

今天作为数组的第一篇文章,先讲解如上内容,就可以满足下面两道题目的解题要求了。

双指针操作

数组是我们讲解的第一种数据结构,而下来要讲解的双指针,则是我们要学习的第一种解题思路。

这里说到的指针,不同于C的指针,而是一个通过初始化指针变量,对应数组不同index下标,从而在计算工程中移动指针搜索目标的解题思路。

不光是数组,字符串、链表等等也会用到指针。所以这次讲过后,等下次遇到就一带而过了。而指针的解题思路一般分为三类:

  • 首尾指针:范围查找,比如二分搜索等
  • 滑动窗口:指针处在数组同一方向,根据条件移动左右指针,用于获取范围和等
  • 快慢指针: 多用于链表计算时,判断是否有环等

先来一道题热热身吧!

剑指OfferII006.排序数组中两个数字之和

https://leetcode-cn.com/problems/kLl5u1/solution/shua-chuan-jian-zhi-offer-day05-shu-zu-i-ygiw/

难度:简单

题目

给定一个已按照 升序排列 的整数数组 numbers ,请你从数组中找出两个数满足相加之和等于目标数 target 。

函数应该以长度为 2 的整数数组的形式返回这两个数的下标值。numbers 的下标 从 0 开始计数 ,
所以答案数组应当满足 0 <= answer[0] < answer[1] < numbers.length 。

假设数组中存在且只存在一对符合条件的数字,同时一个数字不能使用两次。

提示:

  • 2 <= numbers.length <= 3 * 10 ^ 4
  • -1000 <= numbers[i] <= 1000
  • numbers 按 递增顺序 排列
  • -1000 <= target <= 1000
  • 仅存在一个有效答案

示例

示例 1:
输入:numbers = [1,2,4,6,10], target = 8
输出:[1,3]

解释:2 与 6 之和等于目标数 8 。因此 index1 = 1, index2 = 3 。
示例 2:
输入:numbers = [2,3,4], target = 6
输出:[0,2]

示例 3:
输入:numbers = [-1,0], target = -1
输出:[0,1]

分析

这可能是力扣有史以来最简单的一道题了,为什么?因为力扣第一题两数之和就跟这道题神似。
但是注意,两数之和是无序的,而这道题是有序的,所以是不是更简单呢?

当然两数之和可以使用的暴力双层循环和Hash表,这道题也一样可以。
但双层for循环是O(n ^ 2)的复杂度,而Hash表虽然时间复杂度是O(n),但空间复杂度一样是O(n).

然而,对于有序数组的两数之和,我们可以使用刚才提到的首尾双指针的解题思路来完成该题目。

  1. 首先设置left、right指针分别指向数组的首、尾
  2. 判断number[left] + number[right] 的和total与target的大小
  3. 如果total > target,right 右移
  4. 如果total < target,left 左移
  5. 由于结果必然存在,最终返回left和right即可

时间复杂度O(n),空间复杂度O(1)

解题

Python:

class Solution:
    def twoSum(self, numbers, target):
        left, right = 0, len(numbers) - 1
        while left < right:
            if numbers[left] + numbers[right] == target:
                return [left, right]
            elif numbers[left] + numbers[right] > target:
                right -= 1
            else:
                left += 1

Java:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int left = 0;
        int right = numbers.length - 1;
        while (left < right) {
            int total = numbers[left] + numbers[right];
            if (total == target) {
                return new int[]{left, right};
            } else if (total > target) {
                right--;
            } else {
                left++;
            }
        }
        return new int[2];
    }
}

这道无脑入门题目,由于过于简单,就不细致讨论了,下来看一道进阶版本的题目。只要上面的题目理解了,这道题注意下细节,即可轻松解题!

剑指OfferII007.数组中和为0的三个数

https://leetcode-cn.com/problems/1fGaJU/solution/shua-chuan-jian-zhi-offer-day05-shu-zu-i-e2af/

难度:中等

题目

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

提示:

  • 0 <= nums.length <= 3000
  • -10 ^ 5 <= nums[i] <= 10 ^ 5

示例

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

示例 3:
输入:nums = [0]
输出:[]

分析

这道题算是上一道题的升华版,那么三层for循环是不是可以走一波?按照这个数量级有点风险,但这种暴力解法就不展开了。
我们打算接着使用刚才双指针的思想,但是三个数该如何操作?无非是在双指针的外面套一层for循环而已。
这道题的数组是没有排序的,所以在前面单独介绍了Python和Java数组的排序操作。
另外需要注意的是,题目要求不重复的三元组,为了保持不重复,我们可以:

  1. 使用集合进行去重
  2. 指针偏移时,判断下一个val是否等于当前val,若等于则跳过的操作来实现
    理解了这三点,有前一道题目解析的铺垫,就可以大胆操作了!
    最后,还有一个优化点,如果第一个数字已经大于0了,那直接可以break了...

解题

Python:

class Solution:
    def threeSum(self, nums):
        nums.sort()
        lg, ret = len(nums), []
        for i in range(lg - 2):
            if nums[i] > 0:
                break
            if i > 0 and nums[i] == nums[i - 1]:
                continue
            left = i + 1
            right = lg - 1
            while left < right:
                total = nums[i] + nums[left] + nums[right]
                if total == 0:
                    ret.append([nums[i], nums[left], nums[right]])
                    val = nums[left]
                    while left < right and nums[left] == val:
                        left += 1
                elif total > 0:
                    right -= 1
                else:
                    left += 1
        return ret

Java:

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

课后习题

既然咱们已经做了两数之和、三数之和,那么留一个简单的作业吧:

力扣 18.四数值和 走起来吧。

欢迎关注我的公众号: 清风Python,带你每日学习Python算法刷题的同时,了解更多python小知识。

我的个人博客:https://qingfengpython.cn

力扣解题合集:https://github.com/BreezePython/AlgorithmMarkdown

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值