• 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

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

  • 相关阅读:
    IntelliJ IDEA 快捷键大全
    springboot整合jsp 遇到的问题
    mysql数据库中某字段一部分乱码
    Spring-boot整合Redis,遇到的问题
    遍历对象和数组的forEach函数
    获取随机数,要求长度一致的字符串格式
    获取yyyy-mm-dd格式的日期
    JS对象常用API
    数组常用API
    JS中异步和单线程
  • 原文地址:https://www.cnblogs.com/jxl00125/p/11570167.html
Copyright © 2020-2023  润新知