刷穿剑指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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,133评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,682评论 3 390
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,784评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,508评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,603评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,607评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,604评论 3 415
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,359评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,805评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,121评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,280评论 1 344
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,959评论 5 339
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,588评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,206评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,442评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,193评论 2 367
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,144评论 2 352

推荐阅读更多精彩内容

  • 昨日回顾 昨天作为这个系列的第一篇文章,分别介绍了Java与Python的以下内容: 整数类型与考点 二进制与十进...
    清风Python阅读 298评论 0 1
  • to-do:看一下别人写的题解 https://github.com/981377660LMT/algorithm...
    winter_sweetie阅读 742评论 1 0
  • leetcode 链接[https://leetcode-cn.com/problemset/lcof/] 3、数...
    漫彻思特阅读 317评论 0 0
  • day2 14-1 剪绳子 (重点) 思路1:动态规划p94-98 思路2:贪心算法p94-98 课本的代码 14...
    IAmKepler阅读 203评论 0 0
  • day5 50.第一个只出现一次的字符(重点) 思路:哈希表遍历两次字符串s,第一遍统计各个字符的出现次数,第二遍...
    IAmKepler阅读 264评论 0 0