高频面试算法(一)

按照出现的频率次数从高到底的顺序排列

1. (3)无重复字符的最长子串-106

使用滑动窗口来解这个问题。

注意点:remove时是移除left对应的那个字符

    public int lengthOfLongestSubstring(String s) {
        if ((s == null) || s.isEmpty()) {
            return 0;
        }

        int left = 0;
        int right = 0;
        int maxLength = 0;
        Set<Character> chars = new HashSet<>();

        while (right < s.length()) {
            if (!chars.contains(s.charAt(right))) {
                chars.add(s.charAt(right));
                right++;
                maxLength = Math.max(maxLength, right - left);
            } else {
                chars.remove(s.charAt(left));
                left++;
            }
        }
        return maxLength;
    }

2.  (25)K 个一组翻转链表-84

使用一个dummy节点,再反转链表和删除等链表中比较有用,使用了一个反转部分k个链表的函数。

注意:

1. for (; (i<k) && (curr != null); i++) {  移动curr时需要注意还没有到k个curr已经为null了

2. kGroupEnd 这个是指向一组节点的末尾节点,每次反转后需要指向反转后的头节点来连接链表,然后再更新到k个反转的末尾。

3. 由于反转后startKGroup 变成了反转后的末尾节点了。

    public ListNode reverseKGroup(ListNode head, int k) {
        ListNode dummy = new ListNode(0);
        dummy.next = head;

        ListNode kGroupEnd = dummy;
        ListNode curr = head;

        while (curr != null) {
            ListNode startKGroup = curr;
            int i=0;
            for (; (i<k) && (curr != null); i++) {
                curr = curr.next;
            }

            if (i == k) {
                kGroupEnd.next = reverseK(startKGroup, k);
                kGroupEnd = startKGroup;
            } else {
                kGroupEnd.next = startKGroup;
            }
        }
        return dummy.next;
    }

    private ListNode reverseK(ListNode head, int k) {
        ListNode prev = null;
        ListNode curr = head;
        for (int i=0; i<k; i++) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }

3. (206)反转链表-83

方法1:迭代反转链表

    public ListNode reverseList(ListNode head) {
        ListNode prev = null;
        ListNode curr = head;
        while (curr != null) {
            ListNode next = curr.next;
            curr.next = prev;
            prev = curr;
            curr = next;
        }
        return prev;
    }

方法2:递归反转链表

值得注意的递归退出的条件是 head==null或者head.next == null。递归返回的时候head是递归的上一次的head,所以例如  1->2->3->4。这种递归在3节点的时候已经返回, 所以返回时head是3, 

head.next.next = head; 相当于实现了 4->3的转化,同时要切断3->的连接,head.next = null;其实递归实现的是尾插法的方式。

    public ListNode reverseList(ListNode head) {
        if ((head == null) || (head.next == null)) {
            return head;
        }

        ListNode newHead = reverseList(head.next);
        head.next.next = head;
        head.next = null;
        return newHead;
    }

4. (215)数组中的第K个最大元素-81

方法 1:库排序法

    public int findKthLargest(int[] nums, int k) {
        Arrays.sort(nums);
        return nums[nums.length - k];
    }

方法 2:使用优先队列(最小堆)

    public int findKthLargest(int[] nums, int k) {
        PriorityQueue<Integer> priQueue = new PriorityQueue<>();

        for (int i=0; i<nums.length; i++) {
            priQueue.add(nums[i]);
            if (priQueue.size() > k) {
                priQueue.poll();
            }
        }
        return priQueue.peek();
    }

方法 3:快速选择(Quickselect)

直接用快速排序不能通过,快速排序的pivot使用最右边的值,这里使用了随机值进行排序,使用右边作为枢纽元素的话,会出现又大量重复的元素时会超时。

    Random  random = new Random();
    public int findKthLargest(int[] nums, int k) {
        return quickSelect(nums, 0, nums.length -1, nums.length -k);
    }

    private int quickSelect(int[] nums, int left, int right, int index) {
        int pivotIndex = partitionRandom(nums, left, right);
        if (pivotIndex == index) {
            return nums[pivotIndex];
        } else if (pivotIndex < index) {
            return quickSelect(nums, pivotIndex + 1, right, index);
        } else {
            return quickSelect(nums, left, pivotIndex -1, index);
        }
    }

    private int partitionRandom(int[] nums, int left, int right) {
        int pivotIndex = left + random.nextInt(right - left + 1);
        int pivot = nums[pivotIndex];
        swap(nums, pivotIndex, right);

        int i = left;
        for (int j=left; j<right; j++) {
            if (nums[j] < pivot) {
                swap(nums, i, j);
                i++;
            }
        }
        swap(nums, i, right);
        return i;
    }

    private void swap(int[] nums, int i, int j) {
        int temp = nums[i];
        nums[i] = nums[j];
        nums[j] = temp;
    }

5.(15)三数之和-62

注意:

1. 需要先排序,方便去重

2. 数组遍历时的边界,固定元素时length-2,left=i+1, right = lenght -1;

3.right指针是--

    public List<List<Integer>> threeSum(int[] nums) {
        List<List<Integer>> result = new ArrayList<>();
        //先对数组排序,方便去重
        Arrays.sort(nums);

        // 遍历数组,固定一个元素
        for (int i=0; i<nums.length -2; i++) {
            // 跳过重复元素
            if ((i > 0) && (nums[i] == nums[i-1])) {
                continue;
            }

            // 使用双指针查找剩余两个元素
            int left = i+1;
            int right = nums.length -1;
            while (left < right) {
                int sum = nums[i] + nums[left] + nums[right];
                if (sum == 0) {
                    result.add(Arrays.asList(nums[i], nums[left], nums[right]));

                    // 跳过重复的左指针元素
                    while ((left < right) && (nums[left] == nums[left + 1])) {
                        left++;
                    }

                    // 跳过重复的右指针元素
                    while ((left < right) && (nums[right] == nums[right - 1])) {
                        right--;
                    }

                    // 移动双指针
                    left++;
                    right--;
                } else if (sum < 0) {
                    // 如果和小于0,说明需要增大和,移动左指针
                    left++;
                } else {
                    // 如果和大于0,说明需要减小和,移动右指针
                    right--;
                }
            }
        }

        return result;
    }

6.(121)买卖股票的最佳时机-61

如果当前遍历的数组不是最小价格,需要更新一次最小价格,如果比最小价格高,需要更新一次利润

    public int maxProfit(int[] prices) {
        int minPrice = prices[0];
        int maxProfit = 0;

        for (int i=1; i<prices.length; i++) {
            if (minPrice > prices[i]) {
                minPrice = prices[i];
            } else {
                maxProfit = Math.max(maxProfit, prices[i] - minPrice);
            }
        }

        return maxProfit;
    }

7.(160)相交链表-58

核心思想就是当一个链表走到结尾的时候,把另外一个链表赋值给当前链表。如果相交,那么一定会出现某个时刻2个节点相等。

    public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        ListNode pa = headA;
        ListNode pb = headB;
        while (pa != pb) {
            if (pa != null) {
                pa = pa.next;
            } else {
                pa = headB;
            }

            if (pb != null) {
                pb = pb.next;
            } else {
                pb = headA;
            }
        }
        return pa;
    }

8.(1)两数之和-48

hashMap key=一个数值,value=index

    public int[] twoSum(int[] nums, int target) {
        Map<Integer, Integer> map = new HashMap<>();

        int[] result = new int[2];
        for (int i=0; i< nums.length; i++) {
            if (!map.containsKey(target - nums[i])) {
                map.put(nums[i], i);
            } else {
                int index = map.get(target - nums[i]);
                result[0] = index;
                result[1] = i;
            }
        }
        return result;
    }

9.(236)二叉树的最近公共祖先-45

核心思想

1. 当左右子节点都不空的时候,说明他们的上一个节点就是最小的公共祖先

2. 当有一方为空时,说明2个节点在同一个子树上

    public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
        if ((root == null) || (root == p) || (root == q)) {
            return root;
        }

        TreeNode left = lowestCommonAncestor(root.left, p, q);
        TreeNode right = lowestCommonAncestor(root.right, p, q);

        if ((left != null) && (right != null)) {
            return root;
        }

        return (left != null) ? left : right;
    }

10.(53)最大子数组和-39

"最大子数组和" 是经典的算法问题,也叫做 最大子序和 

这取决于 nums[i] 和 f(i−1)+nums[i] 的大小,我们希望获得一个比较大的,于是可以写出这样的动态规划转移方程:

f(i)=max{f(i−1)+nums[i],nums[i]}

    public int maxSubArray(int[] nums) {
        int maxSum = nums[0];
        int currMax = nums[0];
        for (int i=1; i< nums.length; i++) {
            //动态规划转移方程:f(i)=max{f(i−1)+nums[i],nums[i]}
            currMax = Math.max(nums[i], currMax + nums[i]);
            maxSum = Math.max(maxSum, currMax);
        }
        return maxSum;
    }

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值