Day1 数组基础

数组理论基础

1. 数组的下表从0开始

2. 数组的地址是连续的,因此增添元素删除元素需要移动其他元素的地址

3. 二维数组在C++里面地址是连续的,java不是

Day1 数组基础_第1张图片

704. 二分查找

课程链接:代码随想录

题目链接:力扣(LeetCode)官网 - 全球极客挚爱的技术成长平台

思路: 

这道题比较容易想到二分查找,但是具体的边界设定比较模糊,可能需要较长时间思考。

知识:

二分查找类似于迭代,需要有迭代条件和结束条件。

迭代条件在本题中即为left和right标杆的更新,确定的新left和right标杆是下一轮寻找的区域边界。需要利用下一轮的搜寻区域确定条件。例如区域是left - mid - 1,如果右边是开的,那么右边就是mid,如果是闭的,右边就是mid - 1。

结束条件是跳出查找的条件,也就是不满足的条件。这里使用while循环,条件成立就继续循环下去,因此结束条件是满足查找的极限条件。明确这两个条件的含义对解题有很大帮助。例如本题中结束条件是middle = target,极限条件就是只有一个元素,判断是否为target。

感悟:

1. 开与闭:

从上述的两个条件不难看出,利用right和left确定查找区间是需要人为假设的。因此,区间左右是闭还是开需要提前设定。下面,我将分四种情况说明迭代和结束条件的设置。

迭代条件

(middle不是需要查找的)

结束条件

(middle是需要查找的)

左闭右闭 新的边界建立在middle的两侧

right = middle - 1;

left = middle + 1

需要查找的就是闭区间里面的唯一元素(如[2,2],middle为2) left = right
左闭右开 新的左边界建立在middle的旁边,新的右边界在middle上

right = middle;

left = middle + 1

middle就是单元素区间的左边界(如[2,3),middle为2) left = right - 1
左开右闭 新的右边界建立在middle的旁边,新的左边界在middle上

right = middle - 1;

left = middle 

之前middle是左右边界平均值取下界,但是这里需要让middle等于右边界。因此,middle的计算方式为ceil((left+right)/2)。

(如:(1,2],middle为2)

left = right - 1
左开右开 新的右边界建立在middle上,新的左边界在middle上

right = middle;

left = middle 

左右边界夹着middle(如(1,3)middle为2) left = right - 2

同时,初始条件也需要设置好。 开的一边需要向外延伸一个位置。左开为-1,闭为0,右开为size,闭为size-1。

这里最难的是左开右闭。之前错在了让结束条件为左、中、右三个元素,但是这里中和右都可以取到,因此在只有两个元素的情况下会查询不到(相当于有个右永远查不到)。因此需要保证结束条件只有一个可行元素,这时需要改变中的计算方法,从floor变成ceil

2. 防止溢出:

求平均值的时候,先进行加法运算可能会让数据大小溢出。因此,可以利用left + (right - left)/2来进行操作。同时,/2可以写成<<1,简便计算。

代码:

(1)左闭右闭

截取区间:left 至 middle - 1;middle + 1 至 right

最终查询位置:left = target = right,此时 middle = target

int search(vector& nums, int target) {
        int left = 0;
        int right = nums.size() - 1;
        while(left <= right){ // 每个筛选区间为左闭右闭,因此最后一个筛选区间应该是包含一个元素的left=right。
            int middle = left + (right - left)/2; // 防止溢出
            if(nums[middle]target){ // 中间值比目标大,目标在左和中之间
                right = middle - 1;
            }else{
                return middle;
            }
        }
        return -1;
    }

(2)左闭右开

截取区间:left 至 middle;middle + 1 至 right

最终查询位置:left = right - 1,此时 middle = left

int search(vector& nums, int target) {
        int left = 0;
        int right = nums.size();
        while(left < right){ 
            int middle = left + (right - left)/2; // 防止溢出
            if(nums[middle]target){ // 中间值比目标大,目标在左和中之间
                right = middle;
            }else{
                return middle;
            }
        }
        return -1;

(3)左开右开

截取区间:left 至 middle;middle 至 right

最终查询位置:left = right - 2,此时 middle = ( left + right ) / 2

int search(vector& nums, int target) {
        int left = -1;
        int right = nums.size();
        while(left < right - 1){ 
            int middle = left + (right - left)/2; // 防止溢出
            if(nums[middle]target){ // 中间值比目标大,目标在左和中之间
                right = middle;
            }else{
                return middle;
            }
        }
        return -1;
    }

(4)左开右闭

截取区间:left 至 middle - 1;middle 至 right

最终查询位置:left = right - 1,此时 middle = right

int search(vector& nums, int target) {
        int left = -1;
        int right = nums.size()-1;
        while(left < right){ 
            int middle = (left + right + 1)/2;
            if(nums[middle]target){ // 中间值比目标大,目标在左和中之间
                right = middle-1;
            }else{
                return middle;
            }
        }
        return -1;
    }

27. 移除元素

课程链接:​​​​​​​代码随想录

题目链接:代码随想录

思路:

这道题的思路就是暴力求解,一开始没有想到其他方法。

知识:

快慢指针。其逻辑在于,暴力求解需要用双层for循环,在转移元素的过程中耗费了很多时间。如果可以确定元素最终转移的位置,可以直接将元素迁移至那个位置,而不用一步一步去挪动。因此,设置快慢指针,一个用来识别元素,一个用来确定位置。

  • 快指针:寻找新数组的元素 ,新数组就是不含有目标元素的数组
  • 慢指针:指向更新 新数组下标的位置

快指针相当于遍历指针,而慢指针相当于目的指针。快指针的意义在于遍历数组中的所有数,类似于外层for循环,慢指针的目的在于确定该数应该所属的位置,相当于内层for循环。

感悟

1. 快指针与for循环的结合:

我在编写的时候,将for循环用i来遍历,但实际上快指针的作用就是遍历每个元素,因此for循环可以用快指针遍历。如下:

for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex]; // 慢指针移动和指向结合
            }
}

2. 另一种解法:

目前的这种方法,快慢指针都是从头到尾。还有一种方法,让一个指针从头到尾,识别不同的元素(类似慢指针),另一个指针从尾到头,识别相同的元素(类似快指针)。这也是把任务拆解成两个部分进行求解,但个人认为不如第一种方法好理解。

代码:

1. 暴力求解

int removeElement(vector<int>& nums, int val) {
        int size = nums.size();
        for(int i=0; iif(nums[i] == val){
                for(int j=i+1; j-1] = nums[j];
                }
                i--;
                size--;
            }
        }
        return size;
    }

2. 快慢指针:

(1)从头到尾

int removeElement(vector& nums, int val) {
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) {
            if (val != nums[fastIndex]) {
                nums[slowIndex++] = nums[fastIndex];
            }
        }
        return slowIndex;
    }

(2)从头到尾+从尾到头

int removeElement(vector& nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.size() - 1;
        while (leftIndex <= rightIndex) {
            // 找左边等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        return leftIndex;   // leftIndex一定指向了最终数组末尾的下一个元素
    }

你可能感兴趣的:(算法,java,数据结构)