LeetCode热题100之双指针

88 阅读6分钟

因为这些题目是考验双指针的,所以先入为主以双指针的思路进行算法思考

283. 移动零

提示

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

 

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

解答:

moveZeroes1是自己的解答,moveZeroes是别人的进阶解法实在是妙啊,我没想到


思路:采用循环的位置移动法
fun moveZeroes1(nums: IntArray): Unit {
    var left = 0
    var right = nums.size-1
        while (left < right) {
            if (nums[left] != 0) {
                left++
            } else {
                var index1= left
                while (index1<right){
                    nums[index1] = nums[index1+1]
                    index1++
                }
                nums[right] = 0
                right--
            }
        }

}

//这个厉害采用位置置换法,
//相当于假设全部都是0,这个时候如果遇到一个非零的数值那么就将这个非零数值与最先出现的一个原值为0的数值位置进行置换

fun moveZeroes(nums: IntArray): Unit {
    //记录好未被占用的零数值的位置
    var index = 0
    for ((i, value) in nums.withIndex()) {
        if (value != 0) {
            //将前一个最近的0的位置值,赋值给当前的非零位置数
            nums[i] = nums[index]
            //将当前非零数,赋值为上一个前一个最近的原先是0的位置
            nums[index++] = value
        }
    }
}

11. 盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。

找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。

返回容器可以储存的最大水量。

说明: 你不能倾斜容器。

image.png

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

示例 2:

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

解答:

思路:这个就是考验水桶的短板效应,不管你长板有多长,最终装水量是以最短边为准

fun maxArea(height: IntArray): Int {
    var left = 0
    var right = height.size - 1
    var result = 0
    //从两边界开始
    while (left != right) {
        //左边大于右边
        if (height[left] > height[right]) {
            //取最小边的高度,乘以两者距离X,拿到最大容积
            result = max(result,height[right] * (right-left))
            //因为左边大于右边,所以右边需要往左边寻找更高的板子
            right--
        }else{
            //取最小边的高度,乘以两者距离X,拿到最大容积
            result = max(result,height[left] * (right-left))
             //因为右边大于坐边,所以左边需要往右边寻找更高的板子
            left++
        }
    }

    return result

}

15. 三数之和

给你一个整数数组 nums ,判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i != ji != k 且 j != k ,同时还满足 nums[i] + nums[j] + nums[k] == 0 。请

你返回所有和为 0 且不重复的三元组。

注意: 答案中不可以包含重复的三元组。

示例 1:

输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
解释:
nums[0] + nums[1] + nums[2] = (-1) + 0 + 1 = 0 。
nums[1] + nums[2] + nums[4] = 0 + 1 + (-1) = 0 。
nums[0] + nums[3] + nums[4] = (-1) + 2 + (-1) = 0 。
不同的三元组是 [-1,0,1][-1,-1,2] 。
注意,输出的顺序和三元组的顺序并不重要。

解答: 思路:因为使用左右指针,并且求和,很容易想到需要先排下序,拿到左右两边的值,进行求和

  • 令左指针 L=i+1=,右指针 R=n−1,当 L<R时,执行循环:
  • 当 nums[i]+nums[L]+nums[R]==0,记录当前下标值,执行循环,判断左界和右界是否和下一位置重复,去除重复解。
  • 若和大于 0,说明 nums[R]太大,R 左移
  • 若和小于 0,说明 nums[L],L 右移
fun threeSum(nums1: IntArray): List<List<Int>> {
    val nums = nums1.sortedArray()
    var list = ArrayList<List<Int>>()
    //遍历
    nums.forEachIndexed { i, value ->
        if (value > 0) {
            return@forEachIndexed
        }
        //去重
        if(i>0 && nums[i]==nums[i-1]) {
            return@forEachIndexed
        }
        //当前值的下一个值与边界值
        var left = i + 1
        var right = nums.size - 1
        //标记符合标准=0时,记录的left的值,防止重复
        var lastLeft = -1
        //指针还未滑动到边界
        while (left < right) {
            
            if (nums[left] + nums[right] + nums[i] == 0) {
                //防止上次符合条件的值和当前的相同,相当于去重
                if (lastLeft==-1 || nums[lastLeft]!=nums[left]) {
                    var value = ArrayList<Int>()
                    value.add(nums[left])
                    value.add(nums[right])
                    value.add(nums[i])
                    list.add(value)
                    lastLeft = left
                }
                left++
            } else if (nums[left] + nums[right] + nums[i] > 0) {
                right--
            } else if (nums[left] + nums[right] + nums[i] < 0) {
                left++
            }
        }

    }
    return list
}

42. 接雨水

给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。

示例 1:

输入: height = [0,1,0,2,1,0,1,3,2,1,2,1]
输出: 6
解释: 上面是由数组 [0,1,0,2,1,0,1,3,2,1,2,1] 表示的高度图,在这种情况下,可以接 6 个单位的雨水(蓝色部分表示雨水)。 

示例 2:

输入: height = [4,2,0,3,2,5]
输出: 9

解答:

/**
 *这段代码实现了计算柱状图中能装多少水的算法,主要思路是:
 * 1. 从左到右计算每个位置左边最高的柱子高度到max_left数组
 * 2. 从右到左计算每个位置右边最高的柱子高度到max_right数组
 * 3. 遍历每个位置i,取max_left[i]和max_right[i]的最小值min
 * 4. 如果min大于当前柱子height[i],那么这个位置可以装水,装的量为min - height[i]
 * 5. 将每个位置能装的水量累加到sum变量
 * 6. 最后返回sum作为整个柱状图能装水的总量
 * 具体分析:
 * - max_left[i]表示位置i左边最高柱子的高度
 * - max_right[i]表示位置i右边最高柱子的高度
 * - 取max_left[i]和max_right[i]的最小值min,是因为水能填充的高度取决于两边最低的柱子
 * - 用min减去height[i]就是当前位置能装的水量
 * - 遍历每个位置进行计算,累加到sum就是整个图形总容量
 * 所以这段代码很巧妙地利用了两个数组从左右两边遍历,计算出每个位置能装水的量,实现了装水算法
 */

fun trap(height: IntArray): Int {
    var sum = 0
    val max_left = IntArray(height.size)
    val max_right = IntArray(height.size)
    for (i in 1 until height.size - 1) {
        max_left[i] = Math.max(max_left[i - 1], height[i - 1])
    }
    for (i in height.size - 2 downTo 0) {
        max_right[i] = Math.max(max_right[i + 1], height[i + 1])
    }
    for (i in 1 until height.size - 1) {
        val min = Math.min(max_left[i], max_right[i])
        if (min > height[i]) {
            sum = sum + (min - height[i])
        }
    }
    return sum
}

一个hard坐一天