Leetcode --- 数组(双指针)

1.合并两个有序数组(88-易)

题目描述:给你两个有序整数数组 nums1 和 nums2,请你将 nums2 合并到 nums1 中,使 nums1 成为一个有序数组。

初始化 nums1 和 nums2 的元素数量分别为 m 和 n 。你可以假设 nums1 的空间大小等于 m + n,这样它就有足够的空间保存来自 nums2 的元素。

示例

输入:nums1 = [1,2,3,0,0,0], m = 3, nums2 = [2,5,6], n = 3
输出:[1,2,2,3,5,6]

思路:本题比较简单的思路是:进行拼接然后再排序(插入排序或库函数)或者使用额外空间进行存储(使用for循环或者库函数)

System.arraycopy(dataType[] srcArray,int srcIndex,int destArray,int destIndex,int length)

其中,srcArray 表示源数组;srcIndex 表示源数组中的起始索引;destArray 表示目标数组;destIndex 表示目标数组中的起始索引;length 表示要复制的数组长度。

这里最优解是三指针,利用数组有序性,原地的排序,即不使用额外空间。

  • 关键是使用从尾向头开始遍历(三指针)
  • 注意,这时谁大谁先走!

代码

public void merge(int[] nums1, int m, int[] nums2, int n) {
    int i = m - 1, j = n - 1, merge = m + n - 1;
    while (i >= 0 || j >= 0) {
        if (i < 0) {
            nums1[merge--] = nums2[j--];
        } else if (j < 0) {
            nums1[merge--] = nums1[i--];
        } else if (nums1[i] > nums2[j]) {
            nums1[merge--] = nums1[i--];
        } else {
            nums1[merge--] = nums2[j--];
        }
    }
}

2.下一个排列(31-中)

题目描述:实现获取 下一个排列 的函数,算法需要将给定数字序列重新排列成字典序中下一个更大的排列。

如果不存在下一个更大的排列,则将数字重新排列成最小的排列(即升序排列)。要求:必须 原地 修改,只允许使用额外常数空间。

示例

输入:nums = [1,2,3]
输出:[1,3,2]

输入:nums = [3,2,1]
输出:[1,2,3]

思路:本题目标是找下一个更大的排列,双指针,我们可以将算法分为两步:

  • 逆序遍历,当第一次出现 nums[i] < nums[i + 1] ,记录此时的i为index2;然后我们去之前遍历中第一个比 nums[i] 大的数对应的索引index2,交换。
  • 我们已经替换了 i 位置的数,但其后的数一定降序(之前遍历过),根据字典序,我们要将 i 后边的数逆序。

ps:为什么逆序遍历,目的是找最后一个出现升序的元素,然后找刚好比他大的第一个元素替换,符合字典序。

代码

public void nextPermutation(int[] nums) {
    int index1 = -1, index2 = -1;
    for (int i = nums.length - 2; i >= 0; i--) {
        if (nums[i] < nums[i + 1]) {
            index1 = i;
            break;
        }
    }
    // 特判
    if (index1 == -1) {
        reverse(nums, 0, nums.length - 1);
        return;
    }
    for (int i = nums.length - 1; i > index1; i--) {
        if (nums[i] > nums[index1]) {
            index2 = i;
            break;
        }
    }
    swap(nums, index1, index2);
    reverse(nums, index1 + 1, nums.length - 1);
}

public void reverse(int[] nums, int i, int j) {
    while (i < j) {
        swap(nums, i++, j--);
    }
}

public void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

3.盛最多水的容器(11-中)

题目描述:给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

说明:你不能倾斜容器。

示例

输入:[1,8,6,2,5,4,8,3,7]
输出:49 
解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。

输入:height = [1,1]
输出:1

思路

法1:暴力求解,时间复杂度O(n^2),注意:盛水木桶效应。但是遗憾的是超时了。

法2:本题的最优解是双指针,代码简单,主要是理解为什么这样可以:背后思想缩减空间,当双指针位于最两端,这是水的宽度最大:

  • 如果左边的高度小,我们移动左指针(移除左边一个柱子),那问题就是,右指针移动的那些就不考虑了吗?其实,由于左边柱子高度低,所以这是个瓶颈,无论怎么移动右指针,都不会比现在的面积大(宽度在减少)。但是如果移动左指针,那么虽然宽度变小了,但是高度可能增大,这带来了不确定性,需要我们去判断。
  • 同理移除右边一个柱子,分析相同。

代码

// 暴力解
public int maxArea(int[] height) {
    int n = height.length;
    int max = 0;
    for (int i = 0; i < n; ++i) {
        for (int j = i + 1; j < n; ++j) {
            max = Math.max(max, Math.min(height[i], height[j]) * (j - i));
        }
    }
    return max;
}

// 双指针
public int maxArea(int[] height) {
    int l = 0, r = height.length - 1;
    int max = 0;
    while (l < r) {
        max = height[l] < height[r] ?
        Math.max(max, (r - l) * height[l++]):
        Math.max(max, (r - l) * height[r--]);
    }
    return max;
}

4.颜色分类(75-中)

题目描述:给定一个包含红色、白色和蓝色,一共 n 个元素的数组,原地对它们进行排序,使得相同颜色的元素相邻,并按照红色、白色、蓝色顺序排列。此题中,我们使用整数 0、 1 和 2 分别表示红色、白色和蓝色。

示例

输入:nums = [2,0,2,1,1,0]
输出:[0,0,1,1,2,2]

思路:本题本质是荷兰国旗问题(快排的分区过程),在荷兰国旗中,我们是给定值num,判断数组中的值与num的大小,进而确定自己的位置。cur指针负责遍历数组,时间复杂度O(n)。定义两个边界指针zero和two,遍历数组跟新这两个边界。

  • 当前值等于0,相当于推着大于0的部分向后走
  • 当前值等于1(中间元素),cur指针移动
  • 当前值等于2,需要用后边值进行交换后继续比较

代码

public void sortColors(int[] nums) {
    int zero = -1, two = nums.length;
    int cur = 0;
    while (cur < two) {
        if (nums[cur] == 0) {
            swap(nums, ++zero, cur++);
        } else if (nums[cur] == 2) {
            swap(nums, --two, cur);
        } else {
            cur++;
        }
    }
}
public void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}

5.将数组分成和相等的三个部分(1013-易)

题目描述:给你一个整数数组 A,只有可以将其划分为三个和相等的非空部分时才返回 true,否则返回 false。

形式上,如果可以找出索引 i+1 < j 且满足 A[0] + A[1] + ... + A[i] == A[i+1] + A[i+2] + ... + A[j-1] == A[j] + A[j-1] + ... + A[A.length - 1] 就可以将数组三等分。

示例

输入:[0,2,1,-6,6,-7,9,1,2,0,1]
输出:true
解释:0 + 2 + 1 = -6 + 6 - 7 + 9 + 1 = 2 + 0 + 1

思路: 本题先计算数组的总和,如果不能被3整除,直接返回false,否则:

法1:使用双指针遍历,注意遍历终止条件,为 l + 1 < r ,保证数组被划分为三个部分,也就是中间有一个元素的情况。注意细节:左右和初始值设为第一个元素,否则指针提前结束的指针会多移动一次,可能提前退出循环,如[3,3,6,5,-2,2,5,1,-9,4],返回false。

法2:直接查找,当等于target的个数为3,则一定能被分成三部分。其实,可能存在这个个数大于3的情况(目标值target为0的情况),但是我们可以提前终止,否则总和不为0,也就不会出现个数大于3的情况。

代码

// 双指针
public boolean canThreePartsEqualSum(int[] nums) {
        int n = nums.length;
        int sum = 0;

        for (int num : nums) {
            sum += num;
        }
        if (sum % 3 != 0) {
            return false;
        }
        int target = sum / 3;
        int l = 0, r = n - 1;
        int lSum = nums[l], rSum = nums[r];

        while (l + 1 < r) {
            if (lSum == target && rSum == target) {
                return true;
            }
            if (lSum != target) {
                lSum += nums[++l];
            } 
            if (rSum != target) {
                rSum += nums[--r];
            }
        }
        return false;
    }
}

// 直接查找
public boolean canThreePartsEqualSum(int[] nums) {
    int n = nums.length;
    int sum = 0;

    for (int num : nums) {
        sum += num;
    }
    if (sum % 3 != 0) {
        return false;
    }
    int target = sum / 3;
    int flag = 0;
    int curSum = 0;

    for (int num : nums) {
        curSum += num;
        if (curSum == target) {
            flag++;
            curSum = 0;
        }
        if (flag == 3) {
            return true;
        }
    }
    return false;
}

6.有序数组的平方(977-易)

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

示例

输入:nums = [-4,-1,0,3,10]
输出:[0,1,9,16,100]
解释:平方后,数组变为 [16,1,0,9,100]
排序后,数组变为 [0,1,9,16,100]

思路:核心考察点是利用原数组有序,但是可以通过这个题练习一下常见的排序算法。

最优解为双指针,因为结果最大值一定出现在原数组最大值或者最小值,故可以直接使用两个指针从两头进行遍历。

代码

// 直接调库函数
public int[] sortedSquares(int[] nums) {
    int n = nums.length;
    for (int i = 0; i < n; i++) {
        nums[i] = nums[i] * nums[i];
    }
    Arrays.sort(nums);
    return nums;
}

// 双指针
public int[] sortedSquares(int[] nums) {
    int n = nums.length;
    int index = n - 1;
    int[] ans = new int[n];
    for (int i = 0, j = n - 1; i <= j; ) {
        if (nums[i] * nums[i] > nums[j] * nums[j]) {
            ans[index--] = nums[i] * nums[i];
            i++;
        } else {
            ans[index--] = nums[j] * nums[j];
            j--;
        }
    }
    return ans;
}

7.按奇偶排序数组 II(922-易)

题目描述:给定一个非负整数数组 A, A 中一半整数是奇数,一半整数是偶数。

对数组进行排序,以便当 A[i] 为奇数时,i 也是奇数;当 A[i] 为偶数时, i 也是偶数。

你可以返回任何满足上述条件的数组作为答案。

示例

输入:[4,2,5,7]
输出:[4,5,2,7]
解释:[4,7,2,5],[2,5,4,7],[2,7,4,5] 也会被接受。

思路:本题两种解法,适用两种情况。

法1:可以使用额外空间。两遍遍历,奇数放在新数组奇数位置,偶数放在新数组偶数位置。

法2:不使用额外空间,可以原地修改数组。使用奇偶指针,遍历数组,寻找两个都没放对位置的,交换两个数。

代码

// 两遍遍历
public int[] sortArrayByParityII(int[] nums) {
    int n = nums.length;
    int[] tmp = new int[n];
    int index = 0;
    for (int i = 0; i < n; i++) {
        if (nums[i] % 2 == 0) {
            tmp[index] = nums[i];
            index += 2;
        }
    }
    index = 1;
    for (int i = 0; i < n; i++) {
        if (nums[i] % 2 == 1) {
            tmp[index] = nums[i];
            index += 2;
        }
    }
    return tmp;
}

// 快慢指针
public int[] sortArrayByParityII(int[] nums) {
    int n = nums.length;
    int j = 1;
    for (int i = 0; i < n; i += 2) {
        if (nums[i] % 2 == 1) {
            while (nums[j] % 2 == 1) {
                j += 2;
            }
            swap(nums, i, j);
        }
    }
    return nums;
}

public void swap(int[] nums, int i, int j) {
    int tmp = nums[i];
    nums[i] = nums[j];
    nums[j] = tmp;
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,636评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,890评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,680评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,766评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,665评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,045评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,515评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,182评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,334评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,274评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,319评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,002评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,599评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,675评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,917评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,309评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,885评论 2 341

推荐阅读更多精彩内容