面试准备:复习几个关键的高频算法

  • 待办项:

  • 1. 单链表:创建链表,查找某个节点,删除某个节点,新增一个节点,插入一个节点,是否有环,两个链表相交的起始节点,反转链表。

  • 2. 二叉树:创建二叉树,前序遍历/中序遍历/后续遍历,找出二叉树的最大深度。
    参考:二叉树的创建及遍历方法

  • 3. 位运算:

  • 4. 字符串:

  • 参考博客:leetCode热题

1. 把两个有序链表整合成一个新的有序列表

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

public class MergeTwoNodeTest_1 {

    public static class CommonNode<V> {
        public V value;
        public CommonNode next;
    }
    /**
     * 题目:将两个有序的链表,整合出一个有序的链表,从小到大
     */
    public static void main(String[] args) {
        CommonNode commonNode1 = new CommonNode<Integer>();
        commonNode1.value = 1;
        CommonNode commonNode2 = new CommonNode<Integer>();
        commonNode2.value = 2;
        commonNode1.next = commonNode2;

        CommonNode commonNode3 = new CommonNode<Integer>();
        commonNode3.value = 3;
        CommonNode commonNode4 = new CommonNode<Integer>();
        commonNode4.value = 4;
        commonNode3.next = commonNode4;

        CommonNode mergeNode = mergeTwoNodeList(commonNode1, commonNode3);
        while (mergeNode != null) {
            System.out.println(mergeNode.value);
            mergeNode = mergeNode.next;
        }
    }

    private static CommonNode mergeTwoNodeList(CommonNode commonNode1, CommonNode commonNode2) {
        CommonNode headNode = new CommonNode();
        headNode.value = -1;
        CommonNode preNode = headNode;

        while (commonNode1 != null && commonNode2 != null) {
            if ((Integer)commonNode1.value < (Integer)commonNode2.value){
                preNode.next = commonNode1;
                commonNode1 = commonNode1.next;
            } else {
                preNode.next = commonNode2;
                commonNode2 = commonNode2.next;
            }
            preNode = preNode.next;
        }

        if (commonNode1 == null) {
            preNode.next = commonNode2;
        } else {
            preNode.next = commonNode1;
        }
        return headNode.next;
    }
}

2. 两数之和

给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那两个整数,并返回它们的数组下标。每种输入只会对应一个答案。但是,数组中同一个元素在答案里不会重复出现。
可以按任意顺序返回答案。

class Solution {
    public int[] twoSum(int[] nums, int target) {
        //Map<Integer, Integer> map = new HashMap<>();
        Map<Integer,Integer>map =new HashMap<>();
        for(int i = 0; i< nums.length; i++) {
            if(map.containsKey(target - nums[i])) {
                return new int[] {map.get(target-nums[i]),i};
            }
            map.put(nums[i], i);
        }
     return new int[0];
    }
}

3. 有效括号的字符串

给定一个只包括 ‘(’,’)’,’{’,’}’,’[’,’]’ 的字符串 s,判断字符串是否有效。
有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。

示例 4:

输入:s = “([)]”
输出:false

class Solution {
    public boolean isValid(String s) {
        int n = s.length();
        if (n % 2 == 1) {
            return false;
        }
        Map<Character, Character> pairs = new HashMap<Character, Character>() {{
            put(')', '(');
            put(']', '[');
            put('}', '{');
        }};
        Deque<Character> stack = new LinkedList<Character>();
        for (int i = 0; i < n; i++) {
            char ch = s.charAt(i);
            if (pairs.containsKey(ch)) {
                //栈顶元素不是当前元素的左括号
                if (stack.isEmpty() || stack.peek() != pairs.get(ch)) {
                    return false;
                }
                //栈顶元素是当前元素的左括号,出栈
                stack.pop();
            } else {
                //入栈
                stack.push(ch);
            }
        }
        return stack.isEmpty();
    }
}

4. 爬楼梯

假设正在爬楼梯。需要 n 阶才能到达楼顶。每次可以爬 1 或 2 个台阶。有多少种不同的方法可以爬到楼顶?

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1 阶 + 1 阶
2 阶

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。
1 阶 + 1 阶 + 1 阶
1 阶 + 2 阶
2 阶 + 1 阶

思路:想达到第n阶,要么是从第n-1阶爬1次1阶上去,要么是从第n-2阶爬一次2阶上去,所以有 solution(n) = solution(n-1) + solution(n-2)

  • 方案1:动态归化法
public class Solution {
    public int climbStairs(int n) {
        if(n==0)return 0;
        if(n==1)return 1;
        int []a=new int [n+1];

        a[1]=1;
        a[2]=2;
        
        for(int i=3;i<=n;i++){
            a[i]=a[i-1]+a[i-2];
        }
        return a[n];

    }
}
  • 方案2:通过滑动数组进行动态规划
public class Solution {
    public int climbStairs(int n) {
        int p = 0, q = 0, r = 1;
        for (int i = 1; i <= n; ++i) {
            p = q; 
            q = r; 
            r = p + q;
        }
        return r;
    }
};

5、最大子数组和

给你一个整数数组 nums ,请找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组是数组中的一个连续部分。

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

public class MaxSumOfSubListTest_2 {
    public static void main(String[] args) {
        int[] nums = new int[]{1, -2, 3, 4, 5};
        int max = maxSubArray(nums);
    }

    public static int maxSubArray(int[] nums) {
        int pre = 0;
        int preMax = nums[0];
        for (int i = 0; i < nums.length; i++) {
            //比较当前值、当前值+前面连续子数组的最大和值,取两者的最大作为含当前值的最大最大和值
            pre = Math.max(pre + nums[i], nums[i]);
            //比较当前值的最大最大和值、前面连续子数组的最大和值(不含当前值)
            preMax = Math.max(pre, preMax);

            System.out.println("" + (pre == preMax) + ", pre=" + pre + ", preMax=" + preMax);
        }
        return preMax;
    }
}

6. 链表是否有环

给出一个链表的头节点 head ,判断链表中是否有环。

  • 方案1:通过set判断是否有重复(普通人)
public class Solution {
    public boolean hasCycle(ListNode head) {
        Set<ListNode> set = new HashSet<ListNode>();
        while (head != null) {
            if (set.contains(head)) {
                return true;
            }else set.add(head);
            head = head.next;
        }
        return false;
    }
}
  • 方案2:快慢指针法
    思路:同一起点但是不一样速度,最后如果能碰面说明有环。
public class Solution {
    public boolean hasCycle(ListNode head) {
       
        ListNode slow = head;
        ListNode fast = head;
        while (fast!=null && fast.next!=null) {
            slow = slow.next;
            fast = fast.next.next;
            if(fast==slow){
                return true;
            }
        }
        return false;
    }
}

7、无环的相交链表

编写一个程序,找到两个单链表相交的起始节点。

注意:如果两个链表没有交点,则返回 null。不可改变两个链表原有结构。整个链表没有环。尽量满足 O(n) 时间复杂度, O(1) 内存。

思路1:假设链表A由x+y两段长度组成,链表B由z+y两段长度组成,y是两个链路的公共链路,则链路x+y+z的长度 等于链路z+y+x的长度。

public class Solution {
public ListNode getIntersectionNode(ListNode headA, ListNode headB) {
        if(headA==null||headB==null){
            return null;
        }
        ListNode A1=headA;
        ListNode A2=headB;
        //A1、A2走过的长度为x+y+z时,A1恰好等于A2
        while(A1!=A2){
            //链路A1最终走过的长度是x+y+z
            if(A1!=null){
                A1=A1.next;
            }else{
                A1=headB;
            }
            //链路A2最终走过的长度是z+y+x
            if(A2!=null){
                A2=A2.next;
            }else{
                A2=headA;
            }
        }
        return A1;
    }
}

思路2:通过哈希表的contain来判断(空间复杂度增大,不写了)

8、反转单链表

给出单链表的头节点 head ,请反转链表,并返回反转后的链表头结点。

思路:参考博客——详解反转链表

public class ReverseSingleLink {
    public static class CommonNode<V> {
        public V value;
        public CommonNode next;

        public CommonNode() {
        }

        public CommonNode(V value) {
            this.value = value;
        }

        public CommonNode(V value, CommonNode next) {
            this.value = value;
            this.next = next;
        }
    }

    /**
     * 给出单链表的头节点 head ,请反转链表,并返回反转后的链表。
     */
    public static void main(String[] args) {
        CommonNode commonNode3 = new CommonNode<Integer>(3);
        CommonNode commonNode2 = new CommonNode<Integer>(2, commonNode3);
        CommonNode commonNode1 = new CommonNode<Integer>(1, commonNode2);
        CommonNode headNode = new CommonNode<Integer>(0, commonNode1);

        CommonNode reverseHeadNode = reverseLink(headNode);

        while (reverseHeadNode != null) {
            System.out.println(reverseHeadNode.value);
            reverseHeadNode = reverseHeadNode.next;
        }
    }

    private static CommonNode reverseLink(CommonNode head) {
        // 如果链表为null 直接返回null
        if (head == null) {
            return null;
        }
        // 如果链表只有一个节点,直接返回头节点
        if (head.next == null) {
            return head;
        }

        CommonNode pre = null;
        CommonNode cur = head;
        CommonNode temp = null;
        while (cur != null) {
            temp = cur.next;
            cur.next = pre;

            pre = cur;
            cur = temp;
        }
        return pre;
    }
}

9、最长回文子串

给出一个字符串,请输出该字符串的最长回文子串。回文串是正读和反读一样的字符串。

思路1:中心扩展算法(参考博客——如何找到字符串中的最长回文子串?

public String longestPalindrome(String s) {
	if (s == null || s.length() == 0) {
		return "";
    }
    int maxBeginIndex = 0;
    int maxLen = 0;
    for (int cur = 0; cur < s.length(); cur++) {
        //当前批次回文串的长度
        int len = 1;
        //定义两个指针从cur开始
        int left = cur-1;
        int right = cur +1 ;

        //left一直朝左遍历,直到到达数组左边界或者不等于cur
        while (left >= 0 && s.charAt(left) == s.charAt(cur)) {
            left--;
            len++;
        }

        //right一直朝右遍历,直到到达数组右边界或者不等于cur
        while (right < s.length() && s.charAt(cur) == s.charAt(right)) {
            right++;
            len++;
        }

        //判断left处和right处是否相等
        while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(right)) {
            left--;
            right++;
            len = len + 2;
        }

        if (len > maxLen) {
            maxLen = len;
            maxBeginIndex = left;
        }
    }
    return s.substring(maxBeginIndex + 1, maxBeginIndex + maxLen + 1);
}

10、盛最多水的容器

给定一个长度为 n 的整数数组 height 。有 n 条垂线,第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。返回容器可以储存的最大水量。不能倾斜容器。在这里插入图片描述
思路:双指针解法——容器的盛水量取决于容器的底和容器较短的那条高。 容器高度较大的一侧的移动只会造成容器盛水量减小,所以应当移动高度较小一侧的指针,并继续遍历,直至两指针相遇 。

假设有左指针 left 和右指针 right ,且 left 指向的值小于 right
的值,如何移动双指针才能保证最大的盛水量被遍历到,假如我们将右指针左移,则右指针左移后的值和左指针指向的值相比有三种情况:

  • 右指针指向的值大于左指针这种情况下,容器的高取决于左指针,但是底变短了,所以容器盛水量一定变小;
  • 右指针指向的值等于左指针这种情况下,容器的高取决于左指针,但是底变短了,所以容器盛水量一定变小;
  • 右指针指向的值小于左指针这种情况下,容器的高取决于右指针,但是右指针小于左指针,且底也变短了,所以容量盛水量一定变小了.
public class MaxAreaTest_4 {
    public static void main(String[] args) {
        int[] height = new int[]{1, 8, 6, 2, 5, 4, 8, 3, 7};
        int maxArea = maxArea(height);
        System.out.println(maxArea);
    }

    private static int maxArea(int[] height) {
        int maxArea = 0;
        int currentArea = 0;

        int left = 0;
        int right = height.length - 1;

        while (left < right) {
            currentArea = Math.min(height[left], height[right]) * (right - left);
            maxArea = Math.max(maxArea, currentArea);

            if (height[left] < height[right]) {
                left++;
            } else {
                right--;
            }
        }
        return maxArea;
    }
}

11、反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

思路:双指针法

public class ReverseStringTest_5 {
    public static void main(String[] args) {
        //偶数场景验证
        char [] charArray1 = "abcd".toCharArray();
        String result = reverseString(charArray1);
        System.out.println(result);

        //奇数场景验证
        char [] charArray2 = "abcde".toCharArray();
        result = reverseString(charArray2);
        System.out.println(result);
    }

    /**
     * 双指针法
     * */
    public static String reverseString(char [] charArray) {
        if (charArray == null || charArray.length == 0) {
            return "";
        }

        int left = 0;
        int right = charArray.length - 1;
        while (left < right) {
            char temp = charArray[left];
            charArray[left] = charArray[right];
            charArray[right] = temp;
            left++;
            right--;
        }
        return new String(charArray);
    }
}

12、

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

攻城有术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值