ARTS第八周

1.Algorithm:每周至少做一个 leetcode 的算法题
2.Review:阅读并点评至少一篇英文技术文章
3.Tip:学习至少一个技术技巧
4.Share:分享一篇有观点和思考的技术文章

以下是各项的情况:

Algorithm

链接:[LeetCode-18]-4sum

题意: 

给定一个包含 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
");  
  }      
   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

  这周没看到什么有趣或者感觉实用的观点 , 以后补 。

原文地址:https://www.cnblogs.com/jxl00125/p/11570167.html