数组中双指针的应用例题

数组的基础题

数组拷贝

将 int[] arr1 = {10,20,30};拷贝到 arr2 数组, , 要求重新生成一份内存空间即它们的数据空间是独立的

int[] arr1 = {10,20,30};

//创建一个新的数组 arr2,开辟新的数据空间
int[] arr2 = new int[arr1.length];

//遍历 arr1 ,把每个元素拷贝到 arr2 对应的元素位置
for(int i = 0; i < arr1.length; i++) {
    arr2[i] = arr1[i];
}

//修改 arr2, 不会对 arr1 有影响
arr2[0] = 100;

//输出 arr1
System.out.println("====arr1 的元素====");
for(int i = 0; i < arr1.length; i++) {
    System.out.println(arr1[i]);//10,20,30
}

//输出 arr2
System.out.println("====arr2 的元素====");
for(int i = 0; i < arr2.length; i++) {
    System.out.println(arr2[i]);//100,20,30
}

数组反转

通过找规律把arr数组的元素内容反转 {11,22,33,44,55,66} —>{66, 55,44,33,22,11}

  1. 把 arr[0] 和 arr[5] 进行交换 {66,22,33,44,55,11}*
  2. 把 arr[1] 和 arr[4] 进行交换 {66,55,33,44,22,11}
  3. 把 arr[2] 和 arr[3] 进行交换 {66,55,44,33,22,11}
  4. 一共要交换 3 次 = arr.length / 2
  5. 每次交换时,对应的两个下标是 arr[i] 和 arr[arr.length - 1 -i]
//定义一个临时变量
int temp = 0;

//计算数组的长度
int len = arr.length; 
for( int i = 0; i < len / 2; i++) {
    //交换
    temp = arr[len - 1 - i];
    arr[len - 1 - i] = arr[i];
    arr[i] = temp;
}

System.out.println("===翻转后数组===");
for(int i = 0; i < arr.length; i++) {
    System.out.print(arr[i] + "\t");//66,55,44,33,22,11
}

使用逆序赋值方式完成数组内容的反转

  1. 先创建一个新的数组 arr2 ,大小 arr.length
  2. 逆序遍历 arr ,将每个元素拷贝到 新数组 arr2 的元素中(顺序拷贝)
  3. 循环拷贝的过程中建议增加一个循环变量 j -> 0 ->
  4. 当 for 循环结束,arr2 就是一个逆序的数组
  5. 让 arr 指向 arr2 数据空间, 此时 arr 原来的数据空间就没有变量引用会被当做垃圾,销毁
int[] arr = {11, 22, 33, 44, 55, 66};
int[] arr2 = new int[arr.length];

//逆序遍历 arr,增加一个循环变量 j -> 0 -> 5
for(int i = arr.length - 1, j = 0; i >= 0; i--, j++) {
    arr2[j] = arr[i];
}

//让 arr 指向 arr2 数据空间
arr = arr2;

//输出arr
System.out.println("====arr 的元素情况=====");
for(int i = 0; i < arr.length; i++) {
    System.out.print(arr[i] + "\t");
}

数组扩容

实现动态的给数组添加元素效果,并且添加完后用户可以决定是否继续添加

  1. 定义初始数组 int[] arr = {1,2,3} , 数组下标范围是 0-2 , 不能使用arr[3]
  2. 定义一个新的数组 int[] arrNew = new int[arr.length+1];
  3. 遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
  4. 将新添加的元素赋给arrNew 的最后一个元素 arrNew[arrNew.length - 1]
  5. 让 arr 指向 arrNew 那么 原来 arr 数组就被销毁
  6. 创建一个 Scanner 可以接受用户输入
  7. 因为用户什么时候退出,不确定,所以使用 do-while + break 来控制
Scanner myScanner = new Scanner(System.in);

//初始化数组
int[] arr = {1,2,3};

do {
    //创建一个新的数组
    int[] arrNew = new int[arr.length + 1];
    
    //遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
    for(int i = 0; i < arr.length; i++) {
        arrNew[i] = arr[i];
    }
    
    //添加元素
    System.out.println("请输入你要添加的元素");
    int addNum = myScanner.nextInt();
    //把 addNum 赋给 arrNew 最后一个元素
    arrNew[arrNew.length - 1] = addNum;

    //让 arr 指向 arrNew
    arr = arrNew;

    //输出 arr 看看效果
    System.out.println("====arr 扩容后元素情况====");
    for(int i = 0; i < arr.length; i++) {
        System.out.print(arr[i] + "\t");
    }

    //询问用户是否继续添加
    System.out.println("是否继续添加 y/n");
    char key = myScanner.next().charAt(0);
    //如果输入 n ,就结束
    if( key == 'n') { 
        break;
    }
}while(true);

System.out.println("你退出了添加...");

实现动态的对数组进行缩减,提示用户是否继续缩减,每次缩减最后那个元素。当只剩下最后一个元素,提示不能再缩减

Scanner myScanner = new Scanner(System.in);

//初始化数组
int[] arr = {12345};

do {
    if(arr.length > 1) {

        //创建一个新的数组
        int[] arrNew = new int[arr.length - 1];

        //遍历 arr 数组,依次将 arr 的元素拷贝到 arrNew 数组
        for(int i = 0; i < arrNew.length; i++) {
            arrNew[i] = arr[i];
        }

        //让 arr 指向 arrNew
        arr = arrNew;

        //输出 arr 看看效果
        System.out.println("====arr 扩容后元素情况====");
        for(int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + "\t");
        }

        //询问用户是否继续添加
        System.out.println("是否继续缩减 y/n");
        char key = myScanner.next().charAt(0);
        //如果输入 n ,就结束
        if( key == 'n') { 
            break;
        }

    } else{
        System.out.println("只剩下一个元素...");  
        break;
    }


}while(true);

System.out.println("你退出了添加...");

打印数组元素

创建一个 char 类型的 26 个元素的数组,分别 放置’A’-‘Z’。使用 for 循环访问所有元素并打印出来。提示:char 类型数据运算 ‘A’+2 -> 'C

  1. 定义一个 数组 char[] chars = new char[26]
  2. 因为 ‘A’ + 1 = ‘B’ 类推,所以使用 for 来赋值
  3. 使用 for 循环访问所有元素
char[] chars = new char[26];

for( int i = 0; i < chars.length; i++) {//循环 26 次
    //chars 是 char[]类型
    //chars[i] 是 char类型
    //'A' + i 是 int , 需要强制转换
    chars[i] = (char)('A' + i); 
}

//循环输出
System.out.println("===chars 数组===");
for( int i = 0; i < chars.length; i++) {//循环 26 次
    System.out.print(chars[i] + " ");
}

杨辉三角

使用二维数组打印一个10行杨辉三角

  • 第一行有一个元素,第n行有n个元素,每一行的第一个元素和最后一个元素都是
  • 从第三行开始,对于非第一个元素和最后一个元素的值 arr[i] [j] = arr[i-1] [j] + arr[i -1] [j-1]
1
1 1
1 2 1
1 3 3 1 
int[][] yangHui = new int[12][];
//遍历 yangHui 的每个元素
for(int i = 0; i < yangHui.length; i++) {
    //给每个一维数组(行)开空间
    yangHui[i] = new int[i+1];
    
    //给每个一维数组(行)赋值
    for(int j = 0; j < yangHui[i].length; j++){
        //每一行的第一个元素和最后一个元素都是1
        if(j == 0 || j == yangHui[i].length - 1) {
            yangHui[i][j] = 1;
        } else {//中间的元素
            yangHui[i][j] = yangHui[i-1][j] + yangHui[i-1][j-1];
        }
    }
}

//输出杨辉三角
for(int i = 0; i < yangHui.length; i++) {
    for(int j = 0; j < yangHui[i].length; j++) {//遍历输出该行
        System.out.print(yangHui[i][j] + "\t");
    }
    System.out.println();
}

插入元素

已知有个升序的数组 , 要求插入一个元素 , 该数组顺序依然是升序

数组扩容 + 定位 (可以冒泡但是效率不高)

先确定添加的数应该插入到哪个索引 , 然后扩容

  • 遍历arr数组 , 如果发现 insertNum<=arr[i] , 说明 i 就是要插入的位置
  • 使用index 保留添加数字的插入位置
  • 如果遍历完后 , 没有发现 insertNum<=arr[i] , 说明 index = arr.length (即添加到arr的最后)
//定义原数组
int[] arr = {10,12,45,90}

//要插入的数字
int insertNum = 23;

//使用index , 保留添加数字的插入位置
int index = -1;


//遍历arr数组
for(int i = 0 ; i < arr.length; i++) {
    if(insertNum <= arr[i]) {
        index = i;
        //找到位置后必须马上退出 , 否则插入的位置会出错
        break;
    }

    if(index == -1){
        //程序走到这里 , 说明还没有找到要添加的位置
        index = arr.length;
    }
}

//创建一个新的数组
int[] arrNew = new int[arr.length + 1];

//i 控制arrNew数组的下标 , j 控制arr数组的下标
for(int i = 0 , j = 0; i < arrNew.length; i++) {
    //可以把arr的元素拷贝到 arrNew
    if(i != index) {
        arrNew[i] = arr[j];
        //只有当拷贝了arr数组中的元素到arrNew时才会执行j++
        j++;

    }else {
        //arrNew只要拷贝到了元素就会执行i++
        arrNew[i] = insertNum;
    }
}

//让 arr 指向 arrNew
arr = arrNew;

//输出 arr 看看效果
System.out.println("====arr插入数据后元素情况====");
for(int i = 0; i < arr.length; i++) {
    System.out.print(arr[i] + "\t");
}

双指针法

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MAKYqVAq-1677514268507)(C:\Users\meng\AppData\Roaming\Typora\typora-user-images\1674549245380.png)]

移除元素

给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于val的元素,并返回移除后数组的新长度。你不需要考虑数组中超出新长度后面的元素

  • 不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。元素的顺序可以改变
  • 示例 2: 给定 nums = [0,1,2,2,3,0,4,2], val = 2, 函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4

暴力的解法: 两层for循环,一个for循环遍历数组元素 ,第二个for循环更新数组

//时间复杂度:O(n^2)
//空间复杂度:O(1)  
int removeElement(vector<int>& nums, int val) {
    int size = nums.size();
    for (int i = 0; i < size; i++) {
        if (nums[i] == val) { // 发现需要移除的元素,就将数组集体向前移动一位
            for (int j = i + 1; j < size; j++) {
                nums[j - 1] = nums[j];
            }
            // 因为下标i以后的数值都向前移动了一位,所以i也向前移动一位 , 表示重新在当前位置往后遍历
            i--; 
            // 此时数组的大小-1
            size--; 
        }
    }
    return size;
}

双指针法(快慢指针法)没有改变元素的相对位置: 两个指针起点相同,只不过一个走的快一个走的慢(满足一定条件)

  • 快指针:遍历原数组寻找新数组的元素 ,快指针会一直更新直到遍历完原数组
  • 慢指针:慢指针的更新取决于快指针指向的元素, 当快指针指向的元素不是待删除的元素时, 快指针才会把其指向的元素赋值给慢指针并更新慢指针
//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
    public int removeElement(int[] nums, int val) {
        // 快慢指针
        int slowIndex = 0;
        for (int fastIndex = 0; fastIndex < nums.length; fastIndex++) {
            if (nums[fastIndex] != val) {
                nums[slowIndex] = nums[fastIndex];
                slowIndex++;
            }
        }
        return slowIndex;
    }
}

相向双指针法: 基于元素顺序可以改变的题目描述, 改变了元素相对位置,确保了移动最少元素

//时间复杂度:O(n)
//空间复杂度:O(1)
class Solution {
    public int removeElement(int[] nums, int val) {
        int leftIndex = 0;
        int rightIndex = nums.length - 1;
        while (leftIndex <= rightIndex) {
            // 找左边第一个等于val的元素
            while (leftIndex <= rightIndex && nums[leftIndex] != val){
                ++leftIndex;
            }
            // 找右边第一个不等于val的元素
            while (leftIndex <= rightIndex && nums[rightIndex] == val) {
                -- rightIndex;
            }
            // 因为在循环内leftIndex或者rightIndex可能一直变化 , 覆盖前需要判断一下
            // 将右边不等于val的元素覆盖左边等于val的元素
            if (leftIndex < rightIndex) {
                nums[leftIndex++] = nums[rightIndex--];
            }
        }
        // leftIndex一定指向了最终数组末尾的下一个元素 , rightIndex小于leftIndex
        return leftIndex;  
    }
}

有序数组的平方

给你一个按非递减顺序排序的整数数组 nums,返回每个数字的平方组成的新数组,要求也按非递减顺序排序

暴力排序

//时间复杂度O(n + nlogn)即O(nlogn)
vector<int> sortedSquares(vector<int>& A) {
    for (int i = 0; i < A.size(); i++) {
        A[i] *= A[i];
    }
    // 快速排序
    Arrays.sort(A); 
    return A;
}

双指针法: 数组是有序的,那么数组元素平方的最大值一定在数组的两端,不是最左边就是最右边不可能是中间,从指针两边开始往中间移动

  • left指向起始位置,right指向终止位置 , 比较这两个指针指向元素的平方的大小 , 将大的数先放入新数组并再移动其对应的指针
  • 定义一个原数组一样的大小的新数组result,定义一个下标指向result数组的终止位置
//时间复杂度O(n)
class Solution {
    public int[] sortedSquares(int[] nums) {
        int right = nums.length - 1;
        int left = 0;
        int[] result = new int[nums.length];
        int index = result.length - 1;
        while (left <= right) {
            if (nums[left] * nums[left] > nums[right] * nums[right]) {
                // 正数的相对位置是不变的, 需要调整的是负数平方后的相对位置
                result[index--] = nums[left] * nums[left];
                ++left;
            } else { //nums[left] * nums[left] <= nums[right] * nums[right]
                result[index--] = nums[right] * nums[right];
                --right;
            }
        }
        return result;
    }
}

长度最小的子数组

给定一个含n个正整数的数组和一个正整数 s ,找出数组元素和 ≥ s 的长度最小的连续子数组并返回其长度。若没有符合条件的子数组返回 0

  • 输入:s = 7, nums = [2,3,1,2,4,3] 输出:2 , 解释:子数组 [4,3] 是该条件下的长度最小的子数组

暴力解法: 一个for循环为滑动窗口的起始位置,一个for循环为滑动窗口的终止位置,不断寻找符合条件的子序列

//时间复杂度:O(n^2)
//空间复杂度:O(1)
int minSubArrayLen(int s, vector<int>& nums) {
    int result = INT32_MAX; // 最终的结果
    int sum = 0; // 子序列的数值之和
    int subLength = 0; // 子序列的长度
    for (int i = 0; i < nums.size(); i++) { // 设置子序列起点为i
        sum = 0;
        for (int j = i; j < nums.size(); j++) { // 设置子序列终止位置为j
            sum += nums[j];
            if (sum >= s) { // 一旦发现子序列和超过了s,更新result
                subLength = j - i + 1; // 取子序列的长度
                result = result < subLength ? result : subLength;
                break; // 因为我们是找符合条件最短的子序列,所以一旦符合条件就break
            }
        }
    }
    // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
    return result == INT32_MAX ? 0 : result;
}

滑动窗口: 用一个for循环根据当前子序列和大小的情况,不断调节子序列的起始位置

  • 窗口: 满足其和 ≥ s 的长度最小的连续子数组
  • 窗口的起始位置如何移动:如果当前窗口的值大于s了,窗口就要向前移动了(缩小窗口的值), 每缩小一次与s的进行比较一次
  • 窗口的结束位置如何移动:窗口的结束位置就是遍历数组的指针,也就是for循环里的索引
//时间复杂度:O(n)
//空间复杂度:O(1)
//不要以为for里放一个while就以为是O(n^2)啊, 主要是看每一个元素被操作的次数
//每个元素在滑动窗后进来操作一次,出去操作一次,每个元素都是被操作两次,所以时间复杂度是 2 × n 也就是O(n)
class Solution {
    public int minSubArrayLen(int s, int[] nums) {
        // 滑动窗口起始位置
        int left = 0;
        // 滑动窗口数值之和
        int sum = 0;
        // 滑动窗口的长度
        int result = Integer.MAX_VALUE;
        // 滑动窗口结束位置
        for (int right = 0; right < nums.length; right++) {
            sum += nums[right];
            // 注意这里使用while,每次更新 left(起始位置),并不断比较子序列是否符合条件
            while (sum >= s) {
                // 取子序列的长度
                result = Math.min(result, right - left + 1);
                // 这里体现出滑动窗口的精髓之处,缩小窗口的值不断变更left(子序列的起始位置)
                sum -= nums[left++];
            }
        }
        // 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
        return result == Integer.MAX_VALUE ? 0 : result;
    }
}

904.水果成篮(opens new window)

76.最小覆盖子串(opens new window)

模拟行为螺旋矩阵

给定一个正整数 n,生成一个包含 1 到 n^2 所有元素,且元素按顺时针顺序螺旋排列的正方形矩阵。

#n=3(奇数)
1  2  3
8  9  4
7  6  5

#n=4(偶数)
1   2   3   4
12  13  14  5
11  16  15  6
10   9   8  7

坚持循环不变量原则 , 对每条边都是以左闭右开的原则处理 , 由外向内一圈一圈这么画下去

class Solution {
    public int[][] generateMatrix(int n) {
        // 控制循环次数同时控制每一圈里每一条边遍历的长度,每次循环右边界收缩一位
        int loop = 0;  
        int[][] res = new int[n][n];
        // 定义每循环一个圈的起始位置(start, start)
        int start = 0;  
        // 定义填充矩阵的数字
        int count = 1;  
        int i, j;

        //例如n为奇数3,那么loop = 1 只是循环一圈,矩阵中间的值需要单独处理
        //loop从1开始
        while (loop++ < n / 2) { // 判断边界后,
             // 模拟填充上行从左到右(左闭右开)
            for (j = start; j < n - loop; j++) {
                res[start][j] = count++;
            }

            // 模拟填充右列从上到下(左闭右开)
            for (i = start; i < n - loop; i++) {
                //此时j == n - loop
                res[i][j] = count++;
            }

            // 模拟填充下行从右到左(左闭右开)
            for (; j >= loop; j--) {
                res[i][j] = count++;
            }

            // 模拟填充左列从下到上(左闭右开)
            for (; i >= loop; i--) {
                res[i][j] = count++;
            }
            
            // 第二圈开始的时候,起始位置要各自加1, 例如:第一圈起始位置是(0, 0),第二圈起始位置是(1, 1)
            start++;
        }

        // 如果n为奇数的话,需要单独给矩阵最中间的位置赋值
        // 矩阵中间的位置,例如:n为3, 中间的位置就是(1,1),n为5,中间位置为(2, 2)
        if (n % 2 == 1) {
            res[start][start] = count;
        }

        return res;
    }
}

找无序数组中个数超过一半的数字

数组中有一个数字出现的次数超过了数组长度的一半,找出这个数字

对数组全部进行排序后,直接输出数组中间元素的值

//时间复杂度Nlogn
public static void main(String[] args){
	Scanner sc = new Scanner(System.in);
	int n = sc.NextInt();
	int[] arr = new int[n];
	for(int i=0;i<arr.length;i++){
		arr[i] = sc.NextInt();
	}
	Arrays.Sort(arr);
	System.out.println(arr[arr.length/2]);
}

消除法: 两个数不同就消掉

//时间复杂度O(N)
static int f(int[] arr){
    //侯选数
	int Val = arr[0];
    //出现的次数
	int count = 1;
    //从第二个元素开始扫描数组
	for(int i = 1;i<arr.length;i++){
        //两两消减为0,应该把现在的元素作为候选值
		if(count==0){
			Val = arr[i];
			count = 1;
			continue;
		}
        //遇到和候选数相同的次数加1
		if(Val == arr[i]){
			count++;
		}else{ //遇到和候选数不同的进行消减
			count--;
		}
	}
	return Val;
}

顺序统计: 确定主元的位置 , 保证左右两边有序

  • 数组中第N/2小的数一定是那个超过一半的数
  • 也可以用hash统计
//时间复杂度O(N)

寻找发帖水王

如果你有一个论坛上所有帖子(包括回帖)的列表,其中帖子作者的ID也在表中,快速找出发帖数目超过了帖子总数的一半的作者

如果出现的次数 , 恰好为总个数的一半

  • 水王占总数的一半 , 说明总数必为偶数 , 极限情况下最后消减完count为0 , 如{5,5,2,1}或 {5,2,5,1}
  • 每次扫描的时候 , 多一个和最后一个元素比较并单独计数的动作 , 如果计数恰好为一半说明水王是最后一个元素 , 如果计数不足一半 说明水王不是最后一个元素,而是留下的那个侯选数 (只有count为0时侯选数才会变化)
static int f(int[] arr){
	int Val = arr[0];
	int count = 1;
    //首先判断第一个元素和最后一个元素, indexlast用来统计和最后一个元素相等的次数(也可以在循环内判断,从0开始),判断最后一个元素是不是水王
	int indexlast = arr[0]==arr[arr.length-1]?1:0;
	for(int i=1;i<arr.length;i++){
        //从第二个元素开始增加和最后一个元素比较的步骤
		if(arr[i]==arr[arr.length-1]){
			indexlast++;
		}
		if(count==0){
			Val = arr[i];
			count = 1;
			continue;
		}
		if(Val==arr[i]){
			count++;
		}else{
			count--;
		}
	}
    //最后一个元素出现N/2,最后一个元素是水王
	if(indexlast==arr.length/2){
		return arr[arr.length-1]
	}else{//否则就是最后一个侯选数
		return Val;
	}
}

最小可用id

在非负数(乱序)中找到最小的可分配的id(从1开始连续编号),数据量1000000 , 在乱序数组中寻找那个空缺的数

暴力循环: 从1开始一次探测每个自然数是否在该数组中

//O(N^2)
static int f(int[] arr){
	int i=1;
	while(i<arr.length){
		if(indexof(arr,i)==-1){
			return i;
		}
	}
}
static int indexof(int[] arr,int k){
	for(int i=0;i<arr.length;i++){
		if(arr[i]==k)
			return 1
	}
	return -1;
}

先排序,然后依次单向扫描数组, 判断每个数字是否在对应的下标

//排序O(nlogn),依次遍历O(N) , 取大的整体算法O(nlgn)
static int f(int[] arr){
	Arrays.Sort(arr);
	for(int i=0;i<arr.length;i++){
		if(arr[i]!=i+1){
			return i+1;
		}
	}
    
    //扫描到最后一个元素都没有缺失的数,直接返回数组长度+1(i=arr.length)
    return i+1;
}

创建辅助空间: 新建长为n+1的数组, 初始值全为0

  • 扫描数组中的元素 , 元素的值是多少在辅助空间的下标就是多少 , 并把该下标处的值改为1
  • 扫描新数组 , 找到第一个数值等于0的元素 , 即原数组缺失的数
//O(N)
static int f(int arr){
	int[] help = new int[arr.length+1];
    //扫描原数组,把数放在新数组中对应的下标
	for(int i = 0;i<arr.length;i++){
        //如果值大于等于数组的长度就每必要考虑放到新数组当中(没有对应下标),因为值都是连续编号的
		if(arr[i]<help.length){
			help[arr[i]] = 1
		}
	}
    //从第二个元素开始扫描新数组,找到第一个没有赋值的元素
	for(int i = 1;i < help.length;i++){
		if(help[i]==0){
			return i;
		}
	}
    //新数组的所有元素都被赋值了
	return help.length+1;
}

static int f(int arr){
	int[] help = new int[arr.length];
	for(int i = 0;i<arr.length;i++){
		if(arr[i]<=help.length){
			help[arr[i]-1] = 1
		}
	}
	for(int i = 0;i < help.length;i++){
		if(help[i]==0){
			return i+1;
		}
	}
	return help.length+1;
}

区间分割法: 假设一个长度为100的数组分为以下三种情况
1.中间值恰好为50时,表明数组左半区间数字元素紧凑,没有要找的缺少元素
2.中间值为大于50的值,表明数组左半区间有漏掉的元素

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DdXzUc10-1677514268508)(C:\Users\meng\AppData\Roaming\Typora\typora-user-images\1674004436757.png)]

public class Smallidfenqu {
	public static int find4(int[] arr,int l,int r) {  //定义数组,左指针,右指针
		if(l>r) return l+1;
        //数组中间元素的下标,用右指针减去左指针加1
		int midIndx=l+((r-l)>>1);   
        //找到数组中第midIndx -l + 1小的元素 ,也就是数组中间位置的值
		int q=selectk(arr,l,r,midIndex-l+1); 
        //t为要求的期望值
		int t=midIndex+1;  
		if(q==t) { //左侧紧密
			return find4(arr,midIndex+1,r);  //在右半区间查找
		}
		else {  //左侧稀疏
			return find4(arr,l,midIndex-1);  //在左半区间查找
		}
	}
  public static void main(String[] args) {
	int[] arr= {1,2,3,4,5,8,9,10,11,10000};
	arr=new int[1000*1000];
  }
}

其他

54.螺旋矩阵

剑指Offer 29.顺时针打印矩阵

26.删除排序数组中的重复项

283.移动零

844.比较含退格的字符串

排序思想的应用

调整数组顺序使奇数位与偶数前面

输入一个整型数组,调整数组中数字的顺序,使得所有奇数位于数组的前半部分,所有偶数位于数组的后半部分,要求时间复杂度O(N)

快排单向扫描法的思想

  • 左指针扫描到奇数则前进
  • 左指针扫描到偶数则和右指针指向的元素交换 , 同时右指针向左前进一步 , 然后左指针继续往前扫描
//时间复杂度O(N)
static void f(int[] arr) {
    int Scan = 0;
    int os = arr.length-1;
    while(Scan<=os) {
        if(arr[Scan]%2!=0) {
            Scan++;
        }else {
            util.swap(arr, Scan, os);
            os--;
        }

    }
}

归并排序的思想

  • 利用一个辅助空间 , 扫描到奇数则放到左指针指向的位置同时指针右移 , 扫描到偶数则放到右指针指向的位置同时指针左移
//时间复杂度O(N)
//空间复杂度O(N)

数组中第k小的数

以尽量高效率求出一个乱序数组中的按数值顺序的第k个元素值

暴力解法

  • 先对乱序数组进行排序 , 然后找到第k个元素值 , 下标对应k-1 , 时间复杂度nlog2n

快排思想:需要改动数组的内容

  • 找到主元的位置,主元的位置就表示主元是第几小的
  • 要找的位置比主元位置大了去右边找 , 要找的位置比主元位置小了去左边找
//时间复杂度O(N) , 最差O(N方)	
static int selectK(int[] arr,int begin,int end,int k) {
        //先找到主元的下标,根据下标看主元是第几小的
		int q = partition(arr,begin,end);
        //第qk小
		int qk = q+1;
		if(qk>k) {//主元大于要找的往左找
			return selectK(arr, begin, q-1, k);
		}else if(qk<k) {//主元小于要找的往右找,右边是个新序列,要在新序列找第k-qk小的
			return selectK(arr, q+1, end, k-qk);
		}
		return arr[q]; 
	}
				
	//找到主元的下标
	static int partition(int[] arr,int begin ,int end) {
		//三点确定法
		int indexMid = (begin+end)>>1;
		if(arr[begin]>=arr[indexMid] && arr[begin]<=arr[end]) {
			indexMid = begin;
		}else if(arr[end]>=arr[begin] && arr[end]<=arr[indexMid]) {
			indexMid = end;
		}
		util.swap(arr, begin, indexMid);
		//把三点确定法确定的值和第一个值交换,这样不影响
		int poivt = arr[begin];
		int left = begin+1;
		int right = end;
		while(left<=right) {
			while(left<=right && arr[left]<=poivt)left++;
			while(left<=right && arr[right]>poivt)right--;
			if(left<right)
				util.swap(arr, left, right);
		}
		util.swap(arr, begin, right);
		return right;
	}

合并有序数组

给定两个排序后的数组A和B,其中A的末端有足够的缓冲空间容纳B , 编写一个方法 , 将B合并入A并排序

归并排序的思想

  • 在A数组中确定两个数组合并后最后一个元素的下标
  • 在A,B两个数组中分别定义两个指针指向数组末尾的元素

逆序对

设 A为一个有n个各不相同数字的有序集(n>1), 如果存在正整数 i , j 使得 1≤ i < j ≤n 而且A[i] > A[j].则<A[i],A[j]>这个有序对称为A的一个逆序对(逆序数)

  • 数组(3,1,4,5,2)有4个逆序(3,1),(3,2),(4,2),(5,2),满足下标小的的对应元素值大于下标大的对应的元素值

在归并排序的过程中 , 只要抓右侧的数,就有逆序对

同时遍历两个数组,对于遍历到的两个数,比较大小。如果arr1数组中的元素比arr2数组中的元素小,就把arr1中的当前元素放入到我们新生成的数组中去,arr1数组的指针往后移一下,反之亦然。

仔细想一下,我们在进行arr1和arr2数组比较的时候,肯定有一种情况是:arr2中的当前元素比arr1中的当前元素小,每当遇上这种情况就计算出arr1数组中当前位置到最后位置的元素的个数类加上,得到的累加和就是最后逆序对的个数

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LlaBCxd6-1677514268509)(D:/%E7%AC%94%E8%AE%B0/%E7%AE%97%E6%B3%95%E9%A2%98/image/%E9%80%86%E5%BA%8F%E5%AF%B9.png)]

int niXu = 0;

int arr[] = { 8, 4, 5, 7, 1, 3, 6, 2 }; 
//归并排序需要一个额外空间
int[] helper = new int[arr.length];
mergeSort(arr, 0, arr.length - 1);

static void MergeSort(int[] arr,int begin,int end){
	if(begin<end){
		int indexMid = (begin+end)>>1;
        //对左侧排序
		MergeSort(arr,begin,indexMid);
        //对右侧排序
		MergeSort(arr,indexMid+1,end);
        //合并
		Merge(arr,begin,indexMid,end);
	}
}
static void Merge(int[] arr,int begin,int Mid,int end){
    //将原始数组的元素拷贝到辅助数组
	System.arraycopy(arr, begin, helper, begin, (end-begin)+1);
    //原始数组的指针(待覆盖数组)
	int current = begin;
    //辅助数组的两个指针
	int left = begin;
	int right = Mid + 1;
    
	while(left<=Mid && right <= end){
		if(helper[left]<=helper[right]){
			arr[current++] = helper[left++];
		}
		else{//右边小
			arr[current++] = helper[right++];
            //只要右边下就加左边元素的个数
            niXu+=mid-left+1;
		}
	}
    //左边的有序序列还有剩余的元素,就全部填充到原始数组
    //右边的有序序列还有剩余的元素没有关系,因为原始数组的剩余元素也是这些
	while(left<=Mid){
		arr[current++] = helper[left++];
	}
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值