ARTS第八周
Posted jxl00125
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ARTS第八周相关的知识,希望对你有一定的参考价值。
1.Algorithm:每周至少做一个 leetcode 的算法题
2.Review:阅读并点评至少一篇英文技术文章
3.Tip:学习至少一个技术技巧
4.Share:分享一篇有观点和思考的技术文章
以下是各项的情况:
Algorithm
题意:
给定一个包含 n 个整数的数组 nums 和一个目标值 target,判断 nums 中是否存在四个元素 a,b,c 和 d ,使得 a + b + c + d 的值与 target 相等?找出所有满足条件且不重复的四元组。
给定数组 nums = [1, 0, -1, 0, -2, 2],和 target = 0。
满足要求的四元组集合为:
[
[-1, 0, 0, 1],
[-2, -1, 1, 2],
[-2, 0, 0, 2]
]
分析:
类似三数之和 , 外层多一层循环
class Solution public List<List<Integer>> fourSum(int[] nums, int target) List<List<Integer>> result = new ArrayList<>(); //先排序 Arrays.sort(nums); for (int i = 0;i < nums.length - 2;i ++) // 去除指针i可能的重复情况 if (i != 0 && nums[i] == nums[i-1]) continue; for (int j = i + 1;j < nums.length;j ++) // 去除j可能重复的情况 if (j != i + 1 && nums[j] == nums[j-1]) continue; int left = j + 1; int right = nums.length -1; while (left < right) // 不满足条件或者重复的,继续遍历 if ((left != j + 1 && nums[left] == nums[left-1]) || nums[i] + nums[j] + nums[left] + nums[right] < target) left ++; else if ((right != nums.length -1 && nums[right] == nums[right + 1]) || nums[i] + nums[j] + nums[left] + nums[right] > target) right --; else List<Integer> list = new ArrayList<>(); list.add(nums[i]); list.add(nums[j]); list.add(nums[left]); list.add(nums[right]); result.add(list); // 满足条件的,进入下一次遍历 left ++; right --; return result;
Review
分享 写伪代码来帮助理清思路 https://www.geeksforgeeks.org/how-to-write-a-pseudo-code/
如何写一个伪代码?
伪代码是经常用于编程和基于算法的字段的术语 。它是一种允许程序员表示算法实现的方法。简单地说,我们可以说它是算法的显性表示 。通常,算法在伪代码的帮助下表示,因为无论什么编程背景或知识等级,程序员都可以解释它们 。顾名思义,伪代码是一种假代码或代码的表示,即使是具有一些学校级编程知识的外行也可以理解 。
算法:它是有组织的逻辑序列或针对特定问题的方法 。程序员实现一种算法来解决问题。算法使用自然语言但有些是用注释来表达 。
伪代码:它只是以简单英语编写的注释和信息文本形式的算法实现 。它没有任何编程语言的语法,因此无法由计算机编译或解释 。
Tip
为什么一般数组都是从0开始编号?(重温数据结构)
首先 重温数组概念 :
1. 数组(Array)就是一种线性表数据结构,用一组连续内存空间来存储一组具有相同类型的数据 。
线性表结构 : 数组,链表,队列,栈 非线性表 : 比如二叉树,堆,图等
2. 连续的内存空间和相同类型的数据
因为这两个特性或者说是限制,拥有一个性质 : “随机访问”。 顾名思义 , 可以随机地去访问数据 , 但相应的, 让数组的很多操作变的非常低效, 比如想在数组中删除,插入一个数据 , 运气不好就必须后移大量数据 。
拓展 : 因为当插入一个新元素时,如果插入末尾,那么此时时间复杂度为O(1),所以如果要将某个数组插入到第K个位置,为了避免大量的数据迁移,我们有一个简单的方法就是:直接将第K位置数据搬移到数组的最后,把新元素直接放入到第K个位置 。 这种处理思想也会在快速排序中使用到 。
同理 , 删除操作 , 比如数组A[10]删除前三个元素 , 为了避免后面元素要搬移三次 ,我们可以先记录下已经删除数据 . 每次删除操作并不是真正的搬移数据,只是记录数据已经被删除 。 当数组没有更多空间去存储数据时候, 我们才
触发真正的删除操作 , 删掉前三个元素 , 剩下原数组其他元素向前移动三个位置 即可 。 而这种思路正是JVM标记清楚垃圾回收算法的核心思想 。
其次 我们需要警惕数组的访问越界问题
例如 : 下面一段C语言代码 乍看上去没有问题
int main(int args,char* arg[]) int i = 0 ; int arr[3] = 0; for(; i<=3 ; i++) arr[i] = 0; printf("hello world\n"); return 0 ;
看起来会是输出三次hello world , 实际上是无线循环打印hello world ,这是为什么 ?
实际因为 数组大小为3 : A[0] , A[1 , A[2] 。 而代码中for循环的结束条件写成了 i<=3 而非 i<3 , 导致当i=3时候 , A[3]访问越界 。 C 语言中 , 只要不是访问首先的内存 ,所有的内存空间都是可以自由访问的 。 根据数组寻址公式 , A[3]也会被定为到某块不属于数组的内存地址上 , 而这个地址如果正好是存储变量 i 的内存地址 , 那么A[3]=0 就相当于 i=0 , 就会导致代码无限循环 。
数组越界在C语言是因为没有规定数组访问越界时候 , 编译器应该如何处理 。因为 ,访问数组的本质就是访问一段连续内存 , 只要数组通过偏移计算得到的内存地址可用 , 那么程序就可能不会报任何错误 。 而JAVA就不会像C一样 , 把数组检查的工作丢给程序员去判断 , 也因此会出现我们日常中头疼仅次于java.lang.NullPointerException: null 的 错误异常 : java.lang.ArrayhIndexOutOfBoundsException 。
比如 这段代码 :
int[] a = new int[3]; a[3] = 10 ;
就会抛出java.lang.ArrayhIndexOutOfBoundsException
最后 回到提出的问题 : 这些都了解后 就可以来思考 为什么数组要从0开始编号 , 而非从1 ?
从数组的内存模型来看 , 下标意味着"偏移"(offset) 。如果用 A 来表示数组的首地址 , A[0] 代表偏移为0 的位置 , 即是首地址 ,而 A[K] 就代表偏移K个type_size的位置 ,所以 计算 A[K]的内存地址 :
A[K]_address = base_address + K * type_size
但是从1开始计数的话 , 计算 A[K]的内存地址 的公式就变为 :
A[K]_address = base_address + (K-1) * type_size
从1开始编号 , 每次随机访问数组元素都多了一次减法运算 , 对于 CPU 来说 , 就是多了一次减法指令 。 为了提高效率 , 减少这一次不必要的减法操作 , 数组从0 开始编号变成约定俗成的 ,甚至是默认的规则 。 另一方面也是因为历史原因 : C语言设计就是从0开始编号 ,其他语言为了减少程序员学习难度就直接照搬 。 实际上, 虽然大多数编程语言 都默认数组从0开始 , 但还是有不少例外的 , 例如 python 就支持负数下标 (但数组下标仍然默认从0开始记数 , 举这个例子是为了说明虽然Python从功能上可以支持默认从负数下标开始 , 但为了减少学习难度 还是从0开始) , MATLAB 就是从1开始计数 。
Share
以上是关于ARTS第八周的主要内容,如果未能解决你的问题,请参考以下文章