leetcodet题解

2019/05/04 Algorithm

练练leetcode上的题目,顺便学学英语。

目录

数组

283. Move Zeroes

Given an array nums, write a function to move all 0's to the end of it while maintaining the relative order of the non-zero elements.

Example:

Input: [0,1,0,3,12]
Output: [1,3,12,0,0]
Note:

You must do this in-place without making a copy of the array.
Minimize the total number of operations.

给定一个数组,写一个函数将0元素移动到数组末尾,同时保持非0元素的位置不变。

思路:

给定一个下标变量k。然后判断当前数组位置下标的值是否为0,不为0和k位置数组值交换,然后k++。为0,不动,数组下标后移,
k记录的是第一个0元素的位置。
/**
* 移动0
* @param arr
*/
public static int[] moveZero(int[] arr)
{
    if (null == arr || arr.length <= 1)
    {
        return null;
    }

    //k记得是第一个0元素的位置
    int k = 0;
    for (int i = 0, length = arr.length; i < length; i++)
    {
        //如果当前不为0,那么和第一个0元素位置交换。
        //如果当前为0则保持不动,导致所有的0都会紧挨着。
        if (arr[i] != 0)
        {
            swap(arr, i, k);
            //所有的0都会紧挨着,k加1代表第一个0元素的位置挪到下一位
            k++;
        }
    }

    return arr;
}

239. 滑动窗口最大值

给定一个数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。
滑动窗口每次只向右移动一位。返回滑动窗口中的最大值。

输入: nums = [1,3,-1,-3,5,3,6,7], 和 k = 3
输出: [3,3,5,5,6,7] 
解释: 

  滑动窗口的位置                最大值
---------------               -----
[1  3  -1] -3  5  3  6  7       3
 1 [3  -1  -3] 5  3  6  7       3
 1  3 [-1  -3  5] 3  6  7       5
 1  3  -1 [-3  5  3] 6  7       5
 1  3  -1  -3 [5  3  6] 7       6
 1  3  -1  -3  5 [3  6  7]      7

思路:

一.滑动窗口,其实就是两个下标(l 和 r)控制窗口里数量大小,其中l是左下边,r是右下标。
  l和r都是只能++不让--, r控制的是进入窗口的数字,l控制的是离开窗口的数字,l <= r。

二.返回滑动窗口的最大值:
  1.最简单暴力的方法,当然是直接遍历窗口里的数字进行比较。
  2.用一个双端队列:
    a.核心思想,维护所有可能成为最大值的数组索引的优先级队列!!!
    b.队列记录数组索引,存的是数组索引,但是比较的是数组里的值,队列从头部到尾部是用索引取数组对应值从大到小排序。
	c.窗口下标r增加让新进来的数从队列尾部进来。新进来的数当前索引最大,所以如果比较值还是最大,那么队列里其它元素都可以弹出,
	  其它索引没有机会成为最大了,只保留当前索引。如果新进来的数最小,那么直接添加到队列尾部,随着l增加有机会成为最大。
	  如果是一个中间值,那么把比它小的值的索引从队列弹出,加入到对应位置。
	d.l增加说明有元素出窗口,如果当前l的索引刚好是队列头部索引,那么队列头部元素出队列,出窗口了。

实现如下:

public static void main(String[] args)
{
    int[] arr = new int[]{1, 9, 8, 3, 5, 2};
    int wl = 3;

    int[] windowMaxValues = getWindowMaxValues(arr, wl);
    if (null != windowMaxValues)
    {
        for (int i = 0, length = windowMaxValues.length; i < length; i++)
        {
            System.out.print(windowMaxValues[i] + " ");
        }
    }
}

/**
     * 四个要素:
     * 1.举例规律会发现最多会有length - k + 1个窗口
     * 2.优先级队列的维护
     * 3.i - k >= 0 窗口出值,队列更新
     * 4.i - k + 1 >= 0 窗口形成,需要获取当前窗口最大值
     * @param nums
     * @param k
     * @return
     */
    public static int[] getWindowMaxValues(int[] nums, int k)
    {
        if (null == nums || nums.length < k || k <= 0)
        {
            return null;
        }

        LinkedList<Integer> queue = new LinkedList<>();
        int length = nums.length;
        //举例规律会发现最多会有length - k + 1个窗口
        int[] maxValues = new int[length - k + 1];
        //用i和长度k来控制窗口的左下标和右下标
        for (int i = 0; i < length; i++)
        {
            //如果当前队列不为空,并且尾元素小于当前索引元素(用索引取对应数组值)
            while (!queue.isEmpty() && nums[queue.peekLast()] <= nums[i])
            {
                queue.pollLast();
            }
            //添加的是索引
            queue.addLast(i);

            //窗口出值,需要判断是否更新队列
            if (i - k >= 0 && !queue.isEmpty())
            {
                //如果当前出窗口的索引,刚好和队列头索引相等,出队列
                if (queue.peekFirst() == i - k)
                {
                    queue.pollFirst();
                }
            }

            //窗口形成,需要获取当前窗口最大值
            if (i - k + 1 >= 0 && !queue.isEmpty())
            {
                maxValues[i - k + 1] = nums[queue.peekFirst()];
            }
        }

        return maxValues;
    }

复杂度:比较平均是O(1)级别,长度N,那么时间复杂度为O(n)。

子数组中最大值与最小值差小于等于定值(滑动窗口变种)

给定一个整型数组arr和一个整数num。某个arr中的子数组sub,
如果想达标,必须满足:
    sub中最大值 - sub中最小值 <= num
返回arr中达标子数组的数量


思路:
    1.用滑动窗口。
    2.窗口满足,那么子数组窗口一定满足,sub中最大值 - sub中最小值 <= num。
    3.窗口不满足,窗口即使继续扩展那么还是不满足条件。

时间复杂读,因为窗口左下标l和右下标r只右递增,没右回退操作,所以时间复杂度和上面一题一样O(n)。

代码:

public class SubArrSatisfyTargetValue
{

    public static void main(String[] args) {
        int[] arr = new int[]{0, 7, 6, 4, 8, 5};
        int num = getSubNumSatisfyCondition(arr, 3);
        System.out.println(num);
    }

    public static int getSubNumSatisfyCondition(int[] arr, int targetValue)
    {
        if (null == arr || arr.length <= 0)
        {
            return 0;
        }

        LinkedList<Integer> maxLinked = new LinkedList<>();
        LinkedList<Integer> minLinked = new LinkedList<>();

        //[l, r) 约定左闭右开, r是第一个不满足的数,那么满足的子数组个数就是r - l
        int l = 0;
        int r = 0;

        //满足条件的个数
        int num = 0;

        //l是开头位置,尝试每个开头
        while (l < arr.length)
        {
            //此时窗口开头是l,r继续尝试向右扩,直到违规
            //注意当l自增后,r是在原有的基础上继续尝试向右扩
            while (r < arr.length)
            {
                //最大队列
                while (!maxLinked.isEmpty() && arr[maxLinked.peekLast()] <= arr[r])
                {
                    maxLinked.pollLast();
                }
                maxLinked.addLast(r);

                //最小队列
                while (!minLinked.isEmpty() && arr[minLinked.peekLast()] >= arr[r])
                {
                    minLinked.pollLast();
                }
                minLinked.addLast(r);

                //如果不满足 最大值 - 最小值 <= targetValue, 跳出while
                int value = arr[maxLinked.getFirst()] - arr[minLinked.getFirst()];
                if (value > targetValue)
                {
                    break;
                }

                r++;
            }

            //返回当前以l为首元素的满足条件的子数组个数
            num += r - l;

            //l自增,换开头,那么队列的首元素过期
            if (maxLinked.getFirst() == l)
            {
                maxLinked.pollFirst();
            }
            if (minLinked.getFirst() == l)
            {
                minLinked.pollFirst();
            }

            //换开头
            l++;
        }

        return num;
    }
}

链表

1.合并有序链表

public static void main(String[] args) {
    // 创建三个有序的 LinkedList
    LinkedList<Integer> l1 = new LinkedList<>();
    l1.add(1);
    l1.add(4);
    l1.add(5);
    LinkedList<Integer> l2 = new LinkedList<>();
    l2.add(1);
    l2.add(3);
    l2.add(4);
    LinkedList<Integer> l3 = new LinkedList<>();
    l3.add(2);
    l3.add(6);

    // 合并三个链表
    LinkedList<Integer> linkedList = mergeTwoLists(mergeTwoLists(l1, l2), l3);
    // 输出合并后的链表
    System.out.println(linkedList);
}


/**
 * 遍历方式
 */
public static LinkedList<Integer> mergeTwoLists(LinkedList<Integer> l1, LinkedList<Integer> l2) {
    LinkedList<Integer> mergedList = new LinkedList<>();
    // l1 和 l2 本身就是有序的,所以只需要比较头节点即可
    while (!l1.isEmpty() && !l2.isEmpty()) {
        // 比较两个链表的头节点,将较小的节点加入到合并后的链表中
        if (l1.peekFirst() <= l2.peekFirst()) {
            mergedList.add(l1.pollFirst());  // 移除并返回l1的第一个元素
        } else {
            mergedList.add(l2.pollFirst());  // 移除并返回l2的第一个元素
        }
    }

    // 将剩余的节点加入到合并后的链表中
    while (!l1.isEmpty()) {
        mergedList.add(l1.pollFirst());
    }

    while (!l2.isEmpty()) {
        mergedList.add(l2.pollFirst());
    }

    return mergedList;
}

2.反转链表

/**
 * 反转链表
 * @author lizhibiao
 * @date 2024/9/25 16:06
 */
public class ReverseLinkedList {

    static class ListNode {
        int val;
        ListNode next;

        ListNode(int val) {
            this.val = val;
        }
    }

    // 迭代反转链表
    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode current = head;

        while (current != null) {
            ListNode next = current.next;  // 暂存下一个节点
            current.next = prev;           // 当前节点指向前一个节点
            prev = current;                // 移动 prev 到当前节点
            current = next;                // 移动 current 到下一个节点
        }

        return prev;  // prev 成为了新的头结点
    }

    public static void main(String[] args) {
        // 示例链表: 1 -> 2 -> 3 -> 4 -> 5
        ListNode head = new ListNode(1);
        head.next = new ListNode(2);
        head.next.next = new ListNode(3);
        head.next.next.next = new ListNode(4);
        head.next.next.next.next = new ListNode(5);

        ReverseLinkedList solution = new ReverseLinkedList();
        ListNode reversedHead = solution.reverseList(head);

        // 输出反转后的链表
        while (reversedHead != null) {
            System.out.print(reversedHead.val + " ");
            reversedHead = reversedHead.next;
        }
    }
}

字符串

动态规划

Search

    Table of Contents