【LeetCode】Java版21-30

代码中用到的数据结构

  • 链表

public class ListNode {
    int val;
    ListNode next;

    ListNode(int val) {
        this.val = val;
    }
}
  • 二叉树

public class TreeNode {
    int val;
    TreeNode left;
    TreeNode right;

    TreeNode(int x) {
        val = x;
    }
}

21. 合并两个有序链表

题目描述:将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。

思路递归:如果 l1 或者 l2 一开始就是 null ,那么没有任何操作需要合并,所以我们只需要返回非空链表。否则,我们要判断 l1 和 l2 哪一个的头元素更小,然后递归地决定下一个添加到结果里的值。
非递归:设定一个哨兵节点 prehead ,维护一个 prev 指针,调整它的 next 指针。然后重复以下过程,直到 l1 或者 l2 指向了 null :如果 l1 当前位置的值小于等于 l2 ,把 l1 的值接在 prev 节点的后面同时将 l1 指针往后移一个。否则,我们对 l2 做同样的操作。不管将哪一个元素接在了后面,都把 prev 向后移一个元素。在循环终止的时候, l1 和 l2 至多有一个是非空的。由于输入的两个链表都是有序的,所以不管哪个链表是非空的,它包含的所有元素都比前面已经合并链表中的所有元素都要大。所以只需要简单地将非空链表接在合并链表的后面,并返回合并链表。

代码实现

  • 解法一:递归,时间复杂度:O(m+n),空间复杂度:O(m+n)

public ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    if (l1 == null) {
        return l2;
    }
    if (l2 == null) {
        return l1;
    }

    ListNode newHead = null;
    if (l1.val < l2.val) {
        newHead = l1;
        newHead.next = mergeTwoLists(l1.next, l2);
    } else {
        newHead = l2;
        newHead.next = mergeTwoLists(l1, l2.next);
    }
    return newHead;
}
  • 解法二:非递归。时间复杂度:O(m+n),空间复杂度:O(1)

public ListNode mergeTwoLists2(ListNode list1, ListNode list2) {
    ListNode preHead = new ListNode(-1);
    ListNode pre = preHead;

    while (list1 != null && list2 != null) {
        if (list1.val < list2.val) {
            pre.next = list1;
            list1 = list1.next;
        } else {
            pre.next = list2;
            list2 = list2.next;
        }
        pre = pre.next;
    }

    pre.next = list1 == null ? list2 : list1;
    return preHead.next;
}

22. 括号生成

题目描述:给出 n代表生成括号的对数,请你写出一个函数,使其能够生成所有可能的并且有效的括号组合。

思路:回溯法

代码实现


public List<String> generateParenthesis(int n) {
    List<String> result = new ArrayList<>();
    backtrack(result, "", 0, 0, n);
    return result;
}

private void backtrack(List<String> result, String cur, int open, int close, int max) {
    if (cur.length() == max * 2) {
        result.add(cur);
        return;
    }

    if (open < max) {
        backtrack(result, cur + "(", open + 1, close, max);
    }
    if (close < open) {
        backtrack(result, cur + ")", open, close + 1, max);
    }
}

23. 合并K个排序链表

题目描述:(hard)合并 k个排序链表,返回合并后的排序链表。请分析和描述算法的复杂度。

思路:分而治之,链表两两合并。时间复杂度:O(n logk),空间复杂度:O(1),其中k 指链表的数目。

代码实现


public ListNode mergeKLists(ListNode[] lists) {
    if (lists == null || lists.length == 0) {
        return null;
    }
    return merge(lists, 0, lists.length - 1);
}

private ListNode merge(ListNode[] lists, int left, int right) {
    if (left == right) {
        return lists[left];
    }
    int mid = left + (right - left) / 2;
    ListNode l1 = merge(lists, left, mid);
    ListNode l2 = merge(lists, mid + 1, right);
    return mergeTwoLists(l1, l2);
}

private ListNode mergeTwoLists(ListNode l1, ListNode l2) {
    if (l1 == null) {
        return l2;
    }
    if (l2 == null) {
        return l1;
    }
    if (l1.val < l2.val) {
        l1.next = mergeTwoLists(l1.next, l2);
        return l1;
    } else {
        l2.next = mergeTwoLists(l1, l2.next);
        return l2;
    }
}

24. 两两交换链表中的节点

题目描述:给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。

思路:定义一个哨兵节点,然后直接交换节点值。或使用递归解法。

代码实现

  • 解法一:直接交换

public ListNode swapPairs(ListNode head) {
    ListNode dummy = new ListNode(0);
    ListNode p = dummy;
    while (head != null) {
        if (head.next != null) {
            ListNode tmp = head.next;
            p.next = tmp;
            head.next = head.next.next;
            tmp.next = head;
            p = p.next.next;
        } else {
            p.next = head;
        }
        head = head.next;
    }
    return dummy.next;
}
  • 解法二:递归

public ListNode swapPairs2(ListNode head) {
    if (head == null || head.next == null) {
        return head;
    }
    ListNode result = head.next;
    head.next = swapPairs(head.next.next);
    result.next = head;
    return result;
}

25. k个一组翻转链表

题目描述:(hard)给出一个链表,每 k个节点一组进行翻转,并返回翻转后的链表。

思路:三种解法:借用队列或栈实现、递归或使用尾插法。

代码实现

  • 解法一:借助栈实现

public ListNode reverseKGroup(ListNode head, int k) {
    Stack<ListNode> stack = new Stack<>();
    ListNode dummy = new ListNode(0);
    ListNode p = dummy;
    while (true) {
        int count = 0;
        ListNode tmp = head;
        while (tmp != null && count < k) {
            stack.push(tmp);
            tmp = tmp.next;
            count++;
        }
        if (count != k) {
            p.next = head;
            break;
        }
        while (!stack.isEmpty()) {
            p.next = stack.pop();
            p = p.next;
        }
        p.next = tmp;
        head = tmp;
    }
    return dummy.next;
}
  • 解法二:递归

public ListNode reverseKGroup3(ListNode head, int k) {
    ListNode cur = head;
    int count = 0;
    while (cur != null && count < k) {
        cur = cur.next;
        count++;
    }
    if (count == k) {
        cur = reverseKGroup3(cur, k);
        while (count > 0) {
            count--;
            ListNode tmp = head.next;
            head.next = cur;
            cur = head;
            head = tmp;
        }
        head = cur;
    }
    return head;
}

26. 删除排序数组中的重复项

题目描述:给定一个排序数组,你需要在原地删除重复出现的元素,使得每个元素只出现一次,返回移除后数组的新长度。

思路: 双指针法。时间复杂度:O(n),空间复杂度:O(1)

代码实现


public int removeDuplicates(int[] nums) {
    if (nums == null || nums.length == 0) {
        return 0;
    }
    int i = 0;
    for (int j = 1; j < nums.length; j++) {
        if (nums[i] != nums[j]) {
            i++;
            nums[i] = nums[j];
        }
    }
    return i + 1;
}

27. 移除元素

题目描述:给定一个数组 nums和一个值val,你需要原地移除所有数值等于 val的元素,返回移除后数组的新长度。

思路:双指针法。时间复杂度:O(n),空间复杂度:O(1)

代码实现


public int removeElement(int[] nums, int val) {
    if (nums == null || nums.length == 0) {
        return 0;
    }

    int length = 0;
    for (int i = 0; i < nums.length; i++) {
        if (nums[i] != val) {
            nums[length++] = nums[i];
        }
    }
    return length;
}

28. 实现strStr()

题目描述:给定一个 haystack字符串和一个 needle字符串,在 haystack字符串中找出 needle字符串出现的第一个位置 (从0开始)。如果不存在,则返回-1。若needle是空字符串则返回0。注:strstr() 函数PHP的函数,用于搜索字符串在另一字符串中是否存在。

思路:KMP算法,让前面匹配的结果指导后面开始的匹配。

代码实现


public int strStr2(String haystack, String needle) {
    char[] hayArr = haystack.toCharArray();
    char[] needArr = needle.toCharArray();
    int i = 0;
    int j = 0;
    while (i < hayArr.length && j < needArr.length) {
        if (hayArr[i] == needArr[j]) {
            i++;
            j++;
        } else {
            i = i - j + 1;
            j = 0;
        }
    }
    if (j == needArr.length) {
        return i - j;
    }
    return -1;
}

29. 两数相除

题目描述:给定两个整数,被除数 dividend和除数divisor。将两数相除,要求不使用乘法、除法和 mod运算符。

思路:使用位运算,找到一个足够大的数,或使用二分查找算法。

代码实现

  • 解法一:位运算

public int divide(int dividend, int divisor) {
    if (dividend == 0) {
        return 0;
    }
    if (dividend == Integer.MIN_VALUE && divisor == -1) {
        return Integer.MAX_VALUE;
    }

    long dividendTmp = Math.abs((long) dividend);
    long divisorTmp = Math.abs((long) divisor);
    int result = 0;
    // 因为数值范围是[−2^31,  2^31 − 1],所以n <= 31;
    for (int n = 31; n >= 0; n--) {
        if ((dividendTmp >> n) >= divisorTmp) {
            // 将结果加上2^n
            result += 1 << n;
            // 将被除数减去 divisor*2^n
            dividendTmp -= divisorTmp << n;
        }
    }
    return (dividend ^ divisor) < 0 ? -result : result;
}
  • 解法二:二分查找

public int divide2(int dividend, int divisor) {
    if (dividend == 0) {
        return 0;
    }
    if (dividend == Integer.MIN_VALUE && divisor == -1) {
        return Integer.MAX_VALUE;
    }

    // divCount 是当前divisorTmp相对于divisor的倍数
    int divCount = 1;
    int result = 0;
    long dividendTmp = Math.abs((long) dividend);
    long divisorTmp = Math.abs((long) divisor);

    while (dividendTmp >= divisorTmp) {
        dividendTmp -= divisorTmp;
        result += divCount;

        if (dividendTmp < Math.abs(divisor)) {
            break;
        }
        // divisorTmp无法倍增时,就将其初始化为divisor绝对值,重新开始下一轮倍增
        if (dividendTmp - divisorTmp < divisorTmp) {
            divisorTmp = Math.abs(divisor);
            divCount = 1;
            continue;
        }
        divisorTmp += divisorTmp;
        divCount += divCount;
    }
    return (dividend ^ divisor) < 0 ? -result : result;
}

30. 串联所有单词的子串

题目描述:(hard)给定一个字符串 s和一些长度相同的单词 words,找出s中恰好可以由words中所有单词串联形成的子串的起始位置。

思路:滑动窗口:由于每个单词长度一样,窗口向右扩展以单词为单位,当遇到不存在的单词,窗口清空,从下一个单词开始匹配,当遇到重复次数过多的单词,窗口左侧收缩到第一次重复的位置的下一个单词,相当于窗口从左侧删除了重复单词,当窗口长度等于单词总长度时,说明遇到了匹配的子串。

代码实现


public List<Integer> findSubstring(String s, String[] words) {
    List<Integer> result = new ArrayList<Integer>();
    if (s == null || words.length == 0) {
        return result;
    }
    int left, right;
    int len = words[0].length();
    int totalLen = len * words.length;
    int n = s.length();
    String str;
    Map<String, Integer> map = new HashMap<>();
    Map<String, Integer> windowMap = new HashMap<>();
    for (String word : words) {
        map.put(word, map.getOrDefault(word, 0) + 1);
    }
    for (int i = 0; i < len; i++) {
        left = right = i;
        windowMap.clear();
        while (left <= n - totalLen && right <= n - len) {
            str = s.substring(right, right + len);
            if (!map.containsKey(str)) {
                left = right + len;
                right = left;
                windowMap.clear();
                continue;
            }
            windowMap.put(str, windowMap.getOrDefault(str, 0) + 1);
            while (windowMap.get(str) > map.get(str)) {
                String rem = s.substring(left, left + len);
                windowMap.put(rem, windowMap.get(rem) - 1);
                left += len;
            }
            right += len;
            if (right - left == totalLen) {
                result.add(left);
                String rem = s.substring(left, left + len);
                windowMap.put(rem, windowMap.get(rem) - 1);
                left += len;
            }
        }
    }
    return result;
}
已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 技术黑板 设计师:CSDN官方博客 返回首页