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次遍历到一个数的时候记录;
· 遍历到的数和上一个赋值的元素比较,相等时跳过,不等时记录。
数组的第1个数我们肯定不会动它的,因此,我们从第2个数(下标为1的数)开始遍历,同时也从下标为1的数开始在输入数组上赋值。首先看到1,和 j 左边一位的数进行比较。1等于1跳过,i右移一位,
i看到的数值是2,2与 j 左边一位的元素1不相等,将2的值赋值到 j 的位置,然后 j 右移一位,指向下一个要赋值的位置,
然后 i 右移一位,i 看到的数值是3,3与 j 左边一位的元素2不相等,将3的值赋值到 j 的位置,然后 j 右移一位接下来按照如此继续…
3-3例二:「力扣」第283题
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题
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;
}
}