《算法不好玩》专题三:循环不变量

3-1循环不变量

循环不变量:在循环的过程中保持不变的性质

循环不变式主要用来帮助我们理解算法的正确性。关于循环不变式,我们必须证明三条性质

  • 初始化:循环的第一次迭代之前,它为真。
  • 保持:如果循环的某次迭代之前它为真,那么下次迭代之前它仍为真。
  • 终止:在循环终止时,不变式为我们提供一个有用的性质,该性质有助于证明算法是正确的。

《算法导论(第3版)》里出现「循环不变量」的地方:插入排序、归并排序、快速排序、优先队列、单源最短路径、最小生成树、……

选择排序的循环不变量
循环不变的性质:区间nums[0…i)里保存了数组里最小的i个元素,并且按照升序排序。

public int[] sortArray(int[] nums) {
    int Len = nums.length;
    for(int i = 0;i< len - 1;i++){
        //在 nums[i..len-1]中选出最小的元索
        int minIndex = i;
        for (int j=i + 1;j< len;j++){
            if (nums[j] < nums[minIndex]){
                minIndex = j;
            }
        }
        swap(nums,minIndex,i);
    }
    return nums;
}
private void swap(int[] nums,int index1,int index2){
    int temp = nums[index1];
    nums[index1] = nums[index2];
    nums[index2] = temp;
}
  • 初始化(循环开始前):i=0,区间nums[0…i)为空区间;
  • 保持(循环过程中):选出nums[i…len)里最小的元素交换到i,在下一轮循环开始之前保持了性质;
  • 终止(循环结束中):nums[0…len-2]有序。

插入排序的循环不变量
循环不变的性质:区间nums[0…i)里保存了输入数组里的前i个元素,并且按照升序排序。

public int[] sortArray(int[] nums) {
    int Len = nums.Length;
    //把 nums[i]插入有序数组 nums[0..i-1]
    for(int i=1;i<len;i++){
        int temp = nums[i];
        int j;
        for(j=i;j>0&&nums[j-1]>temp;j--){
        	nums[j] = nums[j - 1];
        }
    	nums[j] = temp;
    }
    return nums;
}

初始化:i=1,区间nums[0…i)只有一个元素;
保持:把nums[i]插入区间区间nums[0…i)使得nums[0…i]有序,在下一轮循环开始之前保持了性质;
终止:nums[0…len)有序。

3-2例一:「力扣」第26题

26. 删除有序数组中的重复项 - 力扣(LeetCode)

class Solution {
    public int removeDuplicates(int[] nums) {
        int len=nums.length;
        if(len<2){
            return len;
        }
        //nums[0..j]有序,且元素唯一
        //j表示上一个赋值的元素的下标
        int j=0;
        for(int i=1;i<len;i++){
            if(nums[i]!=nums[j]){
                j++;
                nums[j]=nums[i];
            }
        }
        return j+1;
    }
}

· 使用一个变量遍历数组;
· 使用另一个变量在输入数组上赋值;
· 第1次遍历到一个数的时候记录;
· 遍历到的数和上一个赋值的元素比较,相等时跳过,不等时记录。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hGZ1hZfb-1649674981571)(C:\Users\86187\AppData\Roaming\Typora\typora-user-images\image-20220411123420284.png)]
在这里插入图片描述

数组的第1个数我们肯定不会动它的,因此,我们从第2个数(下标为1的数)开始遍历,同时也从下标为1的数开始在输入数组上赋值。首先看到1,和 j 左边一位的数进行比较。1等于1跳过,i右移一位,[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-36zUDviK-1649674981573)(C:\Users\86187\AppData\Roaming\Typora\typora-user-images\image-20220411122216083.png)]

i看到的数值是2,2与 j 左边一位的元素1不相等,将2的值赋值到 j 的位置,然后 j 右移一位,指向下一个要赋值的位置,
在这里插入图片描述
然后 i 右移一位,i 看到的数值是3,3与 j 左边一位的元素2不相等,将3的值赋值到 j 的位置,然后 j 右移一位[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oBlEO3Nh-1649674981574)(C:\Users\86187\AppData\Roaming\Typora\typora-user-images\image-20220411122900278.png)]

接下来按照如此继续…

3-3例二:「力扣」第283题

283. 移动零 - 力扣(LeetCode)

class Solution {
    public void moveZeroes(int[] nums){
        int len=nums.length;
        //循环不变量:nums[0..j]!=0,nums(j..i)=0
        //j指向了上一个赋值的元素的位置
        int j=-1;
        for(int i=0;i<len;i++){
            if(nums[i]!=0){
                j++;
                nums[j]=nums[i];
            }
        }
        for(int i=j+1;i<len;i++){
            nums[i]=0;
        }
    }
}

3-4例三:「力扣」第27题

27. 移除元素 - 力扣(LeetCode)

class Solution {
    public int removeElement(int[] nums,int val){
        int len=nums.length;
        if(len==0){
            return len;
        }
        //循环不变量:nums[0..j]!=val
        //j指向了上一个已经赋值的元素的位置
        int j=-1;
        for(int i=0;i<len;i++){
            if(nums[i]!=val){
                j++;
                nums[j]=nums[i];
            }
        }
        return j+1;
    }
}

3-5例四:「力扣」第80题

80. 删除有序数组中的重复项 II - 力扣(LeetCode)

思路:使用一个变量遍历输入数组,然后再用另一个变量在输入数组上进行赋值。由于输入数组是有序数组,题目要求相同的元素最多出现两次,因此下标之差为2的元素肯定不能相等。

为了保证相同元素最多出现两次,一旦发现下标之差为2的元素的值相等了,我们就应该把当前遍历到的元素丢弃。很容易知道数组的前两个元素一定会被保留,我们从下标2开始遍历和赋值,将遍历到的元素和上上一个赋值的元素进行比较,值不同才赋值,值相同就跳过。(一旦发现有可能造成3个元素的值相等,就阻止)

class Solution {
    public int removeDepulicates(int[] nums){
        int len=nums.length;
        if(len<2){
            return len;
        }
        //循环不变量:nums[0..j]是有序的,并且相同元素最多保留2次
        int j=1;
        for(int i=2;i<len;i++){
            if(nums[i]!=nums[j-1]){
                j++;
                nums[j]=nums[i];
            }
        }
        return j+1;
    }
}
  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值