【leetcode面试题】java完成leetcode面试150题

数组

88. 合并两个有序数组

题目描述

最简单的方法就是创建一个新数组,用空间换时间。将nums1和nums2的小的元素放入新数组中,最后再将新数组的值还给nums1.时间复杂度是O(n+m).但也多出来额外的空间。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i=0, j=0, k;
        int[] res = new int[m+n];

        for(k=0; i<m&&j<n; k++){
            if(nums1[i]<=nums2[j]){
                res[k] = nums1[i++];
            }else{
                res[k] = nums2[j++];
            }
        }
		// 处理剩下的元素,剩下的元素一定大于当前res中的值。
		//有点像归并排序
        while(i<m){
            res[k++] = nums1[i++];
        }
        while(j<n){
            res[k++] = nums2[j++];
        }

        for(i=0; i<m+n; i++){
            nums1[i] = res[i];
        }

    }
}

由于上面会出现额外的空间损耗,所以对代码进行改进。下面的代码是不会出现额外空间的。
思路就是将大的先放到nums1的末尾,初始的指针指在nums1和nums2的最后一位(即m-1和n-1),然后从大到小,从后往前填充nums1数组。

class Solution {
    public void merge(int[] nums1, int m, int[] nums2, int n) {
        int i=m-1,j=n-1,ptr=(m+n-1);

        while(i>=0&&j>=0){
            if(nums1[i]>=nums2[j]){
                nums1[ptr--] = nums1[i--];
            }else{
                nums1[ptr--] = nums2[j--];
            }
        }

        while(j>=0){
            nums1[ptr--] = nums2[j--];
        }
    }
}

27. 移除元素

题目描述

使用双指针,如果i不是val就后移,不需要处理;如果i==val,则i,j互换,同时j前移,将换出去的内容放在后面。j还前移的原因是因为如果不前移,且nums[i]和nums[j]如果是相同的值,则会一直死循环。

双指针往往就是处理数组,对数组进行更改(换位置),删除等。或者链表的操作。
双指针的初始位置有很多种,可以一开始在一起,或者一前一后,或者在初始位置和末尾。

class Solution {
    public int removeElement(int[] nums, int val) {
        int i=0, j=nums.length-1;

        while(i<=j){
            if(nums[i]==val){
                int tmp = nums[i];
                nums[i] = nums[j];
                nums[j--] = tmp;
            }else{
                i++;
            }
       }
       return i;
    }

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

题目描述

实际上就是使用双指针,用fast进行指向的元素和前一个元素进行比较,如果相同,则只移动fast。反之,将fast指向的值赋给slow。最后返回slow。

class Solution {
    public int removeDuplicates(int[] nums) {
        int len = nums.length;
        if(len==0){
            return 0;
        }
        int fast=1,slow=1;
        while(fast<len){
            if(nums[fast]!=nums[fast-1]){
                nums[slow++] = nums[fast];
            }
            fast++;
        }

        return slow;
    }
}

双指针

125. 验证回文串

题目描述

直接去除字符串中的不必要内容,再转为小写,最后用双指针比较即可。

class Solution {
    public boolean isPalindrome(String s) {
        String tmp = s.replaceAll("[^a-zA-Z0-9]", "").toLowerCase();

        int left=0, right=tmp.length()-1;
        while(left<right){
            char c1=tmp.charAt(left), c2=tmp.charAt(right);
            if(c1!=c2){
                return false;
            }else{
                left++;
                right--;
            }
        }

        return true;
    }
}

392. 判断子序列

题目描述

双指针分别指向s和t,逐字对比,如果相同则指针同时向后,如果不同则只有t的指针后移。最终如果s的指针大小等同于s的长度即为true。也就是s的最后一个字符也得到了匹配,导致指针的大小为s的长度。

class Solution {
    public boolean isSubsequence(String s, String t) {
        int i=0,j=0;
        int lens=s.length(), lent=t.length();
        
        while(i<lens&&j<lent){
            char c1=s.charAt(i), c2=t.charAt(j);
            if(c1!=c2){
                j++;
            }else{
                i++;
                j++;
            }
        }

        return i==lens;
    }
}

167. 两数之和 II - 输入有序数组

题目描述
双指针应用于处理数组,如果这里的双指针是固定一个元素,然后向后遍历元素,寻找和为target的结果,那就是低效率的,也不是双指针,只是两重循环罢了,没用到非递减序列的条件。

如果要应用非递减序列,可以把双指针放在一前一后的位置,如果和大于target,则后面的指针前移,反之前面的指针后移。直到找到和为target为止。

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] res = new int[2];
        int i=0, j=numbers.length-1;

        while(i<j){
            int tmp=numbers[i]+numbers[j];
            if(tmp==target){
                res[0]=i+1;
                res[1]=j+1;
                break;
            }else if(tmp>target){
                j--;
            }else{
                i++;
            }
        }
        return res;
    }
}

11. 盛最多水的容器

题目描述
实质上是贪心+双指针,具体的思路在注释里了。

class Solution {
    public int maxArea(int[] height) {
        int max=0,s,len;// s是容积,len是底边长
        int i=0, j=height.length-1;


        // 面积是最短边×两边间隔
        // 从两边进行遍历 每次只移动短的边
        // 因为移动长边,无论如何,面积一定会变小(不过长边移动后是变大还是变小)
        // 移动短边,则可能会变大,因此移动短边才有意义。
        while(i<j){
            len=j-i;
            if(height[i]<height[j]){ // 左边是短边
                s=height[i]*len;
                i++;
            }else{ // 右边是短边
                s=height[j]*len;
                j--;
            }

            max=Math.max(max,s);
        }

        return max;
    }
}

滑动窗口

209. 长度最小的子数组

题目描述

用滑动窗口,如果窗口内值大于target则左边向右移动,小于target则右边右移动。

当sum恰好等于target时,我们需要计算当前子数组的长度,并尝试将其与之前找到的最短长度进行比较,更新res。
当sum大于target时,我们需要缩小窗口,直到子数组的和小于target。在这个过程中,每次sum大于或等于target时,我们都可能得到一个新的、更短的满足条件的子数组长度,因此我们需要在每次缩小窗口时都更新res。

class Solution {
    public int minSubArrayLen(int target, int[] nums) {
        int left = 0, right = 0;
        int sum = 0, len = nums.length;
        int res = Integer.MAX_VALUE;

        while (right < len) {
            sum += nums[right++];
            
            while (sum >= target) {
                res = Math.min(res, right - left);
                sum -= nums[left++];
            }
        }

        return res == Integer.MAX_VALUE ? 0 : res;
    }
}

3. 无重复字符的最长子串

题目描述

滑动窗口的模板大概就是两个循环,外面的循环控制右边界,内部的循环控制左边界。

class Solution {
    public int lengthOfLongestSubstring(String s) {
        int i=0,j=0,len=s.length();
        int res=0,cur=0;
        if(len==0){
            return 0;
        }

        for(; j<len; j++){
            cur+=1;
            for(int k=i; k<j; k++){// 去重
                if(s.charAt(k)==s.charAt(j)){
                    i=k+1;
                    cur=(j-k);
                    break;
                }
            }
            res=Math.max(cur,res);
        }

        return res;
    }
}

哈希表

383. 赎金信

题目描述

首先可以按照HashMap的方式来写,但这样其实效率并不高:

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        Map<Character, Integer> map = new HashMap<>();

        // 获得map
        for(int i=0; i<magazine.length(); i++){
            char key = magazine.charAt(i);
            if(map.containsKey(key)){
                map.put(key,map.get(key)+1);
            }else{
                map.put(key,1);
            }
        }

        // 填充ransomnote
        for(int i=0; i<ransomNote.length(); i++){
            char key = ransomNote.charAt(i);
            if(map.containsKey(key)&&map.get(key)!=0){
                map.put(key,map.get(key)-1);
            }else{
                return false;
            }
        }

        return true;
    }
}

可以改为数组的写法,因为题目里的内容都是小写字母,因此可以直接用数组来表示。在网站跑出的结果有显著提高。

class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        int[] res = new int[26];
        Arrays.fill(res,0);
        int len1=ransomNote.length(), len2=magazine.length();

        for(int i=0; i<len2; i++){
            char c=magazine.charAt(i);
            res[c-'a']+=1;
        }

        for(int i=0; i<len1; i++){
            char c=ransomNote.charAt(i);
            res[c-'a']-=1;
            if(res[c-'a']<0){
                return false;
            }
        }

        return true;
    }
}

205. 同构字符串

题目描述
不能单纯的查看键存在了,值是否冲突,还要查看有值的时候,是否存在对应的键。例如s=bad,t=bab,这种情况b对应b,a对应a,但d对应b这种情况只看键是否存在看不出来,需要看值存在,键是否存在。

class Solution {
    public boolean isIsomorphic(String s, String t) {
        HashMap<Character, Character> map = new HashMap<>();
        int len=t.length();

        for(int i=0; i<len; i++){
            char c1=s.charAt(i), c2=t.charAt(i);
            if(map.containsKey(c1)){ // 检查是否已经包含对应关系
                if(c2!=map.get(c1)){ // 有对应关系,但出现冲突
                    return false;
                }
            }else{
                if (map.containsValue(c2)) { // 有值,但不存在对应的键
                    return false;
                }
                map.put(c1,c2);
            }

        }
        return true;
    }
}

290. 单词规律

题目描述
和上一题类似,不过需要判断一下长度是否相同,且字符串可以用split改为字符串数组,会简单很多。

class Solution {
    public boolean wordPattern(String pattern, String s) {
        String[] words = s.split(" ");
        int len=pattern.length();
        HashMap<Character, String> map = new HashMap<>();

        if(len!=words.length){
            return false;
        }
        for(int i=0; i<len; i++){
            char c=pattern.charAt(i);
            if(map.containsKey(c)){
                if(!map.get(c).equals(words[i])){
                    return false;
                }
            }else{
                if(map.containsValue(words[i])){
                    return false;
                }
                map.put(c, words[i]);
            }
        }

        return true;
    }
}

20. 有效的括号

题目描述
经典括号序,一一对应即可。也可以用HashMap,左右括号分别为键和值。

public class Solution {
    public boolean isValid(String s) {
        Stack<Character> stack = new Stack<>();
        int len=s.length();

        for(int i=0; i<len; i++){
            char c=s.charAt(i);
            if(c=='('||c=='['||c=='{'){
                stack.push(c);
            }else{
                if(stack.isEmpty()){
                    return false;
                }
                char top = stack.pop();
                if((c==')'&&top!='(')||
                    (c==']'&&top!='[')||
                    (c=='}'&&top!='{')){
                        return false;
                    }

            }
        }

        return stack.isEmpty();
    }
}

71. 简化路径

题目链接
这题的思路仍然是栈。唯一说困难的地方就是对字符串的分割和拼接。
分割字符串仍然用split(),像我一样用正则也可以,如果不用正则也可以在for里判断是否为”“而去掉docs中的空白内容。
拼接使用join,join可以将数组的内容用特定字符拼接,就像代码里写的。

class Solution {
    public String simplifyPath(String path) {
        Stack<String> stack = new Stack<>();
        String[] docs = path.substring(1).split("/+");
        int len=docs.length;

        for (String s : docs) {
            if (s.equals("..")) {
                if (!stack.isEmpty()) {
                    stack.pop();
                }
            } else if (!s.equals("") && !s.equals(".")) {
                stack.push(s);
            }
        }

        String res = "/" + String.join("/", stack);

        return res;
    }
}

155. 最小栈

题目链接
没有太多难度(如果不考虑性能),主要能够按照面向对象的思想写出类即可。
用到两个栈,mainStack和普通栈无异,可以模仿普通stack的pop,push,top操作。
minStack则记录最小值,其中的内容是越小的值越趋于栈顶。因此minStack的push操作需要设计,在push的值大于minstack的栈顶元素时,就不需要push。确保可以在常数时间找到最小值。而pop操作也一样,如果pop的值和minStack栈顶元素一样,则minstack也要pop,为了确保最小值能被正确更新。

class MinStack {
    private Stack<Integer> mainStack;
    private Stack<Integer> minStack;

    public MinStack() {
        mainStack = new Stack<>();
        minStack = new Stack<>();
    }
    
    public void push(int val) {
        mainStack.push(val);
        if(minStack.isEmpty()||minStack.peek()>=val){
            minStack.push(val);
        }
    }
    
    public void pop() {
        int n=mainStack.pop();
        if(minStack.peek()==n){
            minStack.pop();
        }
    }
    
    public int top() {
        return mainStack.peek();
    }
    
    public int getMin() {
        return minStack.peek();
    }
}

链表

141. 环形链表

题目描述
实际上就是快慢指针(双指针)的应用。
初始化都为head,且快指针和慢指针的移动都要一开始就进行,否则会一直出现true(因为一开始都在head)。若初始化为head和head.next,则在判断条件上会更麻烦。因此直接初始化为head,判断条件为快指针以及快指针的next不为空即可。

public class Solution {
    public boolean hasCycle(ListNode head) {
        if (head == null || head.next == null) {
            return false;
        }
        ListNode slow=head,fast=head;

        while(fast != null && fast.next != null){
            slow=slow.next;
            fast=fast.next.next;
            if(fast==slow){
                return true;
            }
        }

        return false;
    }
}

21. 合并两个有序链表

题目描述
引入哑节点 dummy 来简化链表的合并操作,最终返回 dummy.next 作为结果链表的头节点。
合并过程:
使用 while(p1 != null && p2 != null) 循环遍历两个链表,按顺序将较小的节点连接到结果链表中。
每次将较小的节点连接到 current.next,并将 current 移动到下一个节点。
处理剩余的节点:循环结束后,可能还剩下未遍历完的链表部分,直接将其连接到结果链表的尾部。
返回结果:最终返回 dummy.next,它指向合并后的链表的头节点。

class Solution {
    public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
        // 创建一个dummy节点
        ListNode dummy = new ListNode(0);
        // 使用current指针来构建新的链表
        ListNode current = dummy;

        // 遍历两个链表,按顺序合并
        while (list1 != null && list2 != null) {
            if (list1.val <= list2.val) {
                current.next = list1;
                list1 = list1.next;
            } else {
                current.next = list2;
                list2 = list2.next;
            }
            current = current.next;
        }

        // 如果有一个链表还有剩余,直接连接到新链表的末尾
        if (list1 != null) {
            current.next = list1;
        } else {
            current.next = list2;
        }

        // 返回合并后的链表头节点
        return dummy.next;  // 跳过dummy节点,返回实际的头节点
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值