面试手撕代码(5)

93. 复原ip地址——回溯

典型dfs,每一个部分可能是1,2,3位数,然后递归下去

public class No93 {
    public List<String> restoreIpAddresses(String s) {
        List<String> ans = new ArrayList<>(); //存储结果
        getAns(s, 0, new StringBuilder(), ans, 0);
        return ans;
    }
    private void getAns(String s, int start, StringBuilder temp, List<String> ans, int count) {
        //如果剩余的长度大于剩下的部分都取 3 位数的长度,那么直接剪枝
        if (s.length() - start > 3 * (4 - count)) 
            return;
        //满足条件就加入:当前刚好到达了末尾
        if (start == s.length()) {
            if (count == 4) {//当前刚好是 4 部分,将结果加入
                ans.add(new String(temp.substring(0, temp.length() - 1)));
            }
            return;
        }
        //需要返回的情况:当前超过末位,或者已经到达了4部分但是还有剩余字符
        if (start > s.length() || count == 4)
            return;
        StringBuilder before = new StringBuilder(temp);//保存的当前的解
        //加入1位数,1位数可以是 0
        temp.append(s.charAt(start) + "" + '.');
        getAns(s, start + 1, temp, ans, count + 1);
        //准备加入二三位数,如果开头是 0,直接结束
        if (s.charAt(start) == '0')
            return;
        //加入2位数,记住判断是否越界
        if (start + 1 < s.length()) {
            temp = new StringBuilder(before);//恢复为之前的解
            temp.append(s.substring(start, start + 2) + "" + '.');
            getAns(s, start + 2, temp, ans, count + 1);
        }
        //加入 3 位数
        if (start + 2 < s.length()) {
            temp = new StringBuilder(before);
            int num = Integer.parseInt(s.substring(start, start + 3));
            if (num >= 0 && num <= 255) {
                temp.append(s.substring(start, start + 3) + "" + '.');
                getAns(s, start + 3, temp, ans, count + 1);
            }
        }

    }
}

468. 验证ip地址

public class No468 {
    public String validIPAddress(String IP) {
        String[] IP4Arr = IP.split("\\.",-1);
        if(IP4Arr.length == 4){
            return isIP4Arr(IP4Arr);
        }

        String[] IP6Arr = IP.split(":",-1);
        if(IP6Arr.length == 8){
            return isIP6Arr(IP6Arr);
        }

        return "Neither";
    }

    public String isIP4Arr(String[] IP4Arr){
        for(String ip : IP4Arr){
            if(ip.length() > 3 || ip.length() <= 0){
                return "Neither";
            }
            for(int i = 0 ;i < ip.length();++i){
                if(!Character.isDigit(ip.charAt(i))){
                    return "Neither";
                }
            }
            int num = Integer.parseInt(ip);
            if(num > 255 || String.valueOf(num).length() != ip.length()){
                return "Neither";
            }
        }
        return "IPv4";
    }

    public String isIP6Arr(String[] IP6Arr){
        for(String ip : IP6Arr){
            if(ip.length() > 4 || ip.length() <= 0){
                return "Neither";
            }
            for(int i = 0 ;i < ip.length();++i){
                char c = ip.charAt(i);
                if(!Character.isDigit(c) && !( 'a' <= c && c <= 'f') && !('A' <= c && c <= 'F')){
                    return "Neither";
                }
            }
        }
        return "IPv6";
    }
}

ip地址字符串转换成32位整数


83. 删除排序链表里的重复元素

判断当前节点和下一个节点是否相等,相等的话就删除下一个节点,并且不更新当前节点
不相等的话就更新当前节点为下一个节点

public class No83 {
    public ListNode deleteDuplicates(ListNode head) {
        ListNode cur = head;
        while(cur!=null && cur.next!=null){
            //相等的话就删除下一个节点
            if(cur.val == cur.next.val){
                cur.next = cur.next.next;
                //不相等就后移
            }else{
                cur = cur.next;
            }
        }
        return head;
    }
}

剑指offer36. 二叉树与双向链表

36题
思路:题目中说生成“排序循环双向链表”,可以考虑用中序遍历来实现,同时为了完成循环至少需要两个指针,设置一个head指针指向链表头,设置一个pre指针遍历,当遍历到root的节点的最左节点的时候,单独处理一下head和pre

class Solution {
    private Node head=null;
    private Node pre=null;
    public Node treeToDoublyList(Node root) {
        if(root == null) return null;
        inorder(root);
        pre.right=head;//设置为循环链表
        head.left=pre;
        return head;
    }
    //中序遍历构造链表
    void inorder(Node cur){
        if(cur == null) return;//越过叶子节点,返回
        inorder(cur.left);
        cur.left=pre;
        if(pre!=null){//pre不是null的时候,pre才有right
            pre.right=cur;
        }
        pre=cur;//更新pre为当前节点
        if(head==null) head=cur;//head是最左的节点
        inorder(cur.right);
    }
}

328. 奇偶链表

将奇节点放在一个链表里,偶链表放在另一个链表里。然后把偶链表接在奇链表的尾部。

public class No328 {
    public ListNode oddEvenList(ListNode head) {
        if(head == null)
            return head;
        ListNode odd = head;
        ListNode even = head.next;
        ListNode evenHead = even;
        while (even != null && even.next != null){
            odd.next = even.next;
            odd = odd.next;
            even.next = odd.next;
            even = even.next;
        }
        odd.next = evenHead;
        return head;
    }
}

560. 和为k的子数组

方法一:暴力枚举

public class No560 {
    
    public int subarraySum2(int[] nums, int k) {
        int count = 0;
        for (int start = 0; start < nums.length; ++start) {
            int sum = 0;
            for (int end = start; end >= 0; --end) {
                sum += nums[end];
                if (sum == k) {
                    count++;
                }
            }
        }
        return count;
    }
 }

方法二:前缀和+hashmap优化

  • 我们定义pre[i]为[0,i]里面的所有的和,于是[j,i]的和转化为求pre[i] - pre[j-1] == k,移项得到pre[j-1] == pre[i] - k
  • 所以只要统计有多少个前缀和为 pre[i]−k 的 pre[j] 即可
  • 我们建立哈希表 mp,以和为键,出现次数为对应的值,记录 pre[i] 出现的次数,从左往右边更新 mp 边计算答案,那么以 i 结尾的答案 \textit{mp}[\textit{pre}[i]-k]mp[pre[i]−k] 即可在 O(1)O(1) 时间内得到。最后的答案即为所有下标结尾的和为 kk 的子数组个数之和。
public class No560 {
    public int subarraySum(int[] nums, int k) {
        int count = 0, pre = 0;
        HashMap< Integer, Integer > mp = new HashMap < > ();
        mp.put(0, 1);
        for (int i = 0; i < nums.length; i++) {
            pre += nums[i];
            if (mp.containsKey(pre - k))
                count += mp.get(pre - k);
            mp.put(pre, mp.getOrDefault(pre, 0) + 1);
        }
        return count;
    }
}

114. 二叉树展开为链表

如果当前节点root有左子节点,将root的右子树移到root左子树的最右的节点上,再把root左子树移动到root右子树,root更新为root的右子节点
如果当前节点root没有左子节点,root更新为root的右子节点

class Solution {
    //先序遍历:若根节点左子树不为空,找到左子树的最右边的节点
    //将根节点的右子树接到左子树的最右边的节点上
    //将根节点左子树接到根节点右子树上
    public void flatten(TreeNode root) {
        while(root != null){
            if(root.left == null){
                root = root.right;
            }else{
                TreeNode pre = root.left;
                while(pre.right != null){
                    pre = pre.right;
                }
                pre.right = root.right;
                root.right = root.left;
                root.left = null;//!!!!
                root = root.right;
            }
        }
    }
}

300. 最长上升子序列

class Solution {
    public int lengthOfLIS(int[] nums){
        if(nums.length==0) return 0;
        int[] dp=new int[nums.length];
        Arrays.fill(dp,1);
        int res=0;
        for(int i=0;i<nums.length;i++){
            for(int j=0;j<i;j++){
                if(nums[i]>nums[j]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
            res=Math.max(res,dp[i]);
        }
        return res;
    }
}

4. 寻找两个正序数组的中位数——二分法

这道题也能用二分法是没有想到的,详细解答请看这位大神评论区的解答https://leetcode-cn.com/problems/median-of-two-sorted-arrays/solution/xiang-xi-tong-su-de-si-lu-fen-xi-duo-jie-fa-by-w-2/
题目要求时间复杂度达到 O(log(m+n),所以只能用二分,精髓就在于每次排除一半不可能是结果的数

class Solution {
    //二分法
    public double findMedianSortedArrays(int[] nums1, int[] nums2) {
        int n = nums1.length;
        int m = nums2.length;
        int left = (n + m + 1) / 2;
        int right = (n + m + 2) / 2;
        //将偶数和奇数的情况合并,如果是奇数,会求两次同样的 k
        return (getKth(nums1, 0, n - 1, nums2, 0, m - 1, left)
                + getKth(nums1, 0, n - 1, nums2, 0, m - 1, right)) * 0.5;
    }

    public int getKth(int[] nums1, int start1, int end1, int[] nums2, int start2, int end2, int k) {
        int len1 = end1 - start1 + 1;
        int len2 = end2 - start2 + 1;
        //保证len1<len2,这样方便减少情况
        if (len1 > len2) return getKth(nums2, start2, end2, nums1, start1, end1, k);
        //len1,剪枝返回
        if (len1 == 0) {
            return nums2[start2 + k - 1];
        }
        //剩下最后一个的时候返回!!!
        if (k == 1) {
            return Math.min(nums1[start1], nums2[start2]);
        }
        //要防止len1比k/2还小
        int i = start1 + Math.min(len1, k / 2) - 1;
        int j = start2 + Math.min(len2, k / 2) - 1;
        if (nums1[i] > nums2[j]) {
            return getKth(nums1, start1, end1, nums2, j + 1, end2, k - (j - start2 + 1));
        } else {
            return getKth(nums1, i + 1, end1, nums2, j + 1, end2, k - (i - start1 + 1));
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值