牛客网字节面试算法刷题记录

目录

NC78 反转链表

NC140 排序

冒泡排序

快排

NC93 设计LRU缓存结构

NC102 在二叉树中找到两个节点的最近公共祖先

NC19 连续子数组的最大和

描述

思路

NC1 大数加法

NC41 最长无重复子数组

NC38 螺旋矩阵

描述

思路

代码

NC17 最长回文子串

描述

思路

代码

NC54 三数之和

描述

思路

代码

NC32 求平方根

描述

代码

NC91 最长上升子序列(三)

描述

思路


NC78 反转链表

public ListNode ReverseList (ListNode head) {
        if(head==null) return head;
        ListNode p=head.next,q,tail=head;
        tail.next = null;
        while(p!=null){
            q = p.next;
            p.next = tail;
            tail = p;
            p = q;
        }
        return tail;
    }

NC140 排序

冒泡排序

public int[] MySort (int[] arr) {
        for(int i=0;i<arr.length-1;i++){
            for(int j=0;j<arr.length-i-1;j++){
                int temp = arr[j];
                if(arr[j]>arr[j+1]){
                    arr[j] = arr[j+1];
                    arr[j+1] = temp;
                }
            }
        }
        return arr;
    }

快排

public int[] MySort (int[] arr) {
        quickSort(arr, 0, arr.length - 1);
        return arr;
    }
    public void quickSort(int[] arr, int low, int high) {
        if (low >= high) {
            return;
        }
        int base = findBase(arr, low, high);
        quickSort(arr, low, base - 1);
        quickSort(arr, base + 1, high);
    }
    public int findBase(int[] arr, int low, int high) {
        int base = arr[low];
        int start = low;
        int end = high;
        while (low < high) {
            while (low < high && arr[high] > base) {
                high--;
            }
            while (low < high && arr[low] <= base) {
                low++;
            }
            if (low < high) {
                int temp = arr[low];
                arr[low] = arr[high];
                arr[high] = temp;
            }
        }
        arr[start] = arr[high];
        arr[high] = base;
        return high;
    }

NC93 设计LRU缓存结构

初始简单思路:每次放新值、取某个值时候,都把它放队列最后面,这样就能把最久未用的挤到队头,这样可以保证每次超过队列容量时,删除值和放新值时间复杂度都是O(1),因此选用一个双端队列。

进一步细化:每次set一个重复值或是get一个值时,都需要将原来的某个节点给他移动到队尾,我们都知道如果是数组类型的话,这个时间复杂度是O(n-i),如何保证这个时间复杂度也是O(1)呢。因此可以使用循环链表保存前序指针和后继指针,为了方便在队首和队尾插入元素,需要两个指针来指明队首和队尾。如何判断这个值存在不存在,肯定要hashmap了,就需要把每次新产生的节点存到hashmap里面,所以需要自己定义一个类型,hashmap key为key,值为自己定义的这个类型。

还需要一个值表明容量剩余。

那么详细来说思路就是:

set函数:1.首先要判断这个key是否已经存在,如果已经存在,那么将map里的值替换掉,并将该key对应的节点从原来位置删掉,再把该节点插入到队尾。

删除逻辑又分成三种:(1)如果是普通节点,就让node.pre.next=node.next,node.next.pre=node.pre,node.pre=null,node.next=null(2)如果该节点本身就是尾节点,说明不需要移动、不做处理;(3)如果是头结点,那么node.pre就是null,如果采用上面的逻辑就会出现空指针异常,此时只能让head=node.next  node.next.pre=null node.next=null。

2.如果不存在,也分为两种情况:(1)队伍满了,需要删除队首,并将该值从hashmap删除,再生成新节点插入队尾,容量不变,仍然为0。(2)队伍还有位置,直接生成新节点插入队尾,容量减1。两种逻辑都不要忘记将新节点的key、value存入到hashmap。

get函数:1.如果存在,找到该节点,将对应节点从原来位置删除,再将该节点插入队尾。(和set部分逻辑一样,可以考虑将删除节点、将某个节点插入到队尾这两段逻辑单独拎出来作为两个函数) 2.如果不存在,返回-1。

import java.util.*;


public class Solution {
//标志队首
    Node head;
//标志队尾
    Node tail;
    class Node {
        public int key;
        public int value;
        public Node pre;
        public Node next;
        public Node(int key, int value, Node pre, Node next) {
            this.value = value;
            this.key = key;
            this.pre = pre;
            this.next = next;
        }
    }
    HashMap<Integer, Node> map = new HashMap<>();
    int count = 0;
    int maxCap = 0;
    public Solution(int capacity) {
        maxCap = capacity;
    }
    public void print(Node n) {
        Node p = n;
        while (p != null) {
            System.out.print("-" + p.value);
            p = p.next;
        }
        System.out.println();
    }

    public int get(int key) {
//如果存在该元素,将该元素节点移动到队尾,原位置删除
        if (map.containsKey(key)) {
            Node delNode = map.get(key);
            Node preNode = delNode.pre;
            Node nextNode = delNode.next;
            //在原位置删除该元素
            //该元素本来就是队尾,直接返回无需换位置
            if (count == 1 || nextNode == null) return delNode.value;
            //如果原来是队首,删除逻辑单独处理
            if (preNode == null) {
                head = nextNode;
                nextNode.pre = null;
                delNode.next = null;
            } else {
                preNode.next = delNode.next;
                delNode.next.pre = preNode;
            }
            //将元素加到队尾
            tail.next = delNode;
            delNode.next = null;
            delNode.pre = tail;
            tail = delNode;
            // System.out.println("----get:-----");
            // print(head);
            // map.forEach((k, v)->System.out.println("k:" + k + "v:" + v.value));
            // System.out.println("输出:" + delNode.value);
            return delNode.value;
        } else {
            //如果不存在返回-1
            // System.out.println("----get:-----");
            // print(head);
            // map.forEach((k, v)->System.out.println("k:" + k + "v:" + v.value));
            // System.out.println("输出:-1");
            return -1;
        }
    }
    public void set(int key, int value) {
        //插入时判断是否已经有该key,有的话需要替换,并将该元素移到队尾
        if (map.containsKey(key)) {
            Node dulNode = map.get(key);
            dulNode.value = value;
            map.put(key, dulNode);

            //在原位置删除该元素
            //该元素本来就是队尾,直接返回无需换位置
            if (count == 1 || dulNode.next == null) return;
            //如果原来是队首,删除逻辑单独处理
            if (dulNode.pre == null) {
                head = dulNode.next;
                dulNode.next.pre = null;
                dulNode.next = null;
            } else {
                dulNode.pre.next = dulNode.next;
                dulNode.next.pre = dulNode.pre;
            }
            //将元素加到队尾
            tail.next = dulNode;
            dulNode.next = null;
            dulNode.pre = tail;
            tail = dulNode;

            // System.out.println("----set:-----");
            // print(head);
            // map.forEach((k, v)->System.out.println("k:" + k + "v:" + v.value));
            // System.out.println("输出:null");
        } else {
            // 插入时判断数量+1是否大于容量
            if (maxCap <= 0) return;
            if (count + 1 > maxCap) {
                //大于容量队首元素出队
                map.remove(head.key);
                Node tempNode = head.next;
                head.next = null;
                head = tempNode;
                head.pre = null;
                count--;
            }
            //新元素入队尾
            Node newNode = new Node(key, value, null, null);
            newNode.pre = tail;
            if (tail != null) {
                tail.next = newNode;
            } else {
                tail = newNode;
                head = newNode;
                head.pre = null;
                tail.pre = null;
                head.next = null;
                tail.next = null;
            }
            tail = newNode;
            count++;
            map.put(key, newNode);
            // System.out.println("----set:-----");
            // print(head);
            // map.forEach((k, v)->System.out.println("k:" + k + "v:" + v.value));
            // System.out.println("输出:null");
        }
    }
}

/**
 * Your Solution object will be instantiated and called as such:
 * Solution solution = new Solution(capacity);
 * int output = solution.get(key);
 * solution.set(key,value);
 */

NC102 在二叉树中找到两个节点的最近公共祖先

 public int lowestCommonAncestor (TreeNode root, int o1, int o2) {
        HashMap<Integer,Integer> hashMap = new HashMap<>();
        List<TreeNode> list = new ArrayList<>();
        list.add(root);
        hashMap.put(root.val,null);
        while(!list.isEmpty()){
            TreeNode node = list.get(0);
            if(node.left!=null){
                 list.add(node.left);
                 hashMap.put(node.left.val,node.val);
            }
            if(node.right!=null){
                list.add(node.right);
                hashMap.put(node.right.val,node.val);
            } 
            list.remove(0);
        }
        List<Integer> pathO1 = new ArrayList<>();
        pathO1.add(o1);
        List<Integer> pathO2 = new ArrayList<>();
        pathO2.add(o2);
        findPath(hashMap,o1,pathO1);
        findPath(hashMap,o2,pathO2);
        pathO1.forEach(p->System.out.print(p+" "));
        System.out.println();
        pathO2.forEach(p->System.out.print(p+" "));
        int index1=pathO1.size()-1;
        int index2=pathO2.size()-1;
        while(index1>=0 && index2>=0 && pathO1.get(index1).equals(pathO2.get(index2))){
            index1--;
            index2--;
        }
        if(index1>=0 && index2>=0){
            return pathO1.get(index1+1);
        }
        if(index1<0){
            return pathO1.get(0);
        }
        if(index2<0){
            return pathO2.get(0);
        }
        return 0;
    }
    public void findPath(HashMap<Integer,Integer> hashMap,Integer o,List<Integer> list){
        while(o!=null){
            if(hashMap.get(o)!=null)
            list.add(hashMap.get(o));
            o = hashMap.get(o);
        }
    }

NC19 连续子数组的最大和

描述

输入一个长度为n的整型数组array,数组中的一个或连续多个整数组成一个子数组,子数组最小长度为1。求所有子数组的和的最大值。

数据范围:

1<=n<=2×105

−100<=a[i]<=100

要求:时间复杂度为O(n),空间复杂度为 O(n)

进阶:时间复杂度为 O(n),空间复杂度为 O(1)

输入[1,-2,3,10,-4,7,2,-5],返回值:18,经分析可知,输入数组的子数组[3,10,-4,7,2]可以求得最大和为18

思路

用一个动态规划数组,每个位置上的人都有两种选择,加入前面的或者不加入,每个位置上的人努力做到自己这和最大,所以会比对上一位的和加上自己的比自己本身手里的大吗,如果打不过就加入,如果加入还不如自己单干就不加入,全程将最大值记录下来,最后就可以得到最优解。

 public int FindGreatestSumOfSubArray (int[] array) {
       int[] dp = new int[array.length];
       dp[0] = array[0];
       int max = dp[0];
       for(int i=1;i<array.length;i++){
        dp[i] = Math.max(dp[i-1]+array[i],array[i]);
        if(dp[i]>max) max = dp[i];
       }
       return max;
    }

NC1 大数加法

这道题注意别超时,不要切割字符串转成int数组,不要用string保存结果再反转,用一个char数组保存。

 public String solve (String s, String t) {
        if (s.length() <= 0)
            return t;
        if (t.length() <= 0)
            return s;
        char[] res = new char[Math.max(s.length(),t.length())];
        int high = 0;
        int index1 = s.length() - 1;
        int index2 = t.length() - 1;
        while (index1 >= 0 && index2 >= 0) {
            int add = s.charAt(index1) - '0' + t.charAt(index2) - '0' + high;
            res[index2] = (char)( add % 10 + '0');
            if (add >= 10) high = 1;
            else high = 0;
            index1--;
            index2--;
        }
        while (index1 >= 0) {
            int add =  s.charAt(index1) - '0' + high;
             res[index1] = (char)( add % 10 + '0');
            if (add >= 10) high = 1;
            else high = 0;
            index1--;
        }
        while (index2 >= 0) {
            int add = t.charAt(index2) - '0' + high;
            res[index2] = (char)( add % 10 + '0');
            if (add >= 10) high = 1;
            else high = 0;
            index2--;
        }
        String result = String.valueOf(res);
        if (high == 1) result = "1" + result;
        return result;
    }

NC41 最长无重复子数组

这题直接暴力法,第一层遍历用i从头到尾,第二层遍历用index从i到尾,如果遍历过程中遇到的map里没有就length加一,加入map,如果遇到重复的,比较length是否大于max,大于则替换,将mao清空,break退出此次。

public int maxLength (int[] arr) {
        int max = -1;
        int length = 0;
        HashMap<Integer, Boolean> hashMap = new HashMap<>();
        for (int i = 0; i < arr.length; i++) {
            for (int index = i; index < arr.length; index++) {
                if (!hashMap.containsKey(arr[index])) {
                    hashMap.put(arr[index], true);
                    length++;
                    if (length > max) max = length;
                } else {
                    hashMap.clear();
                    length = 0;
                    break;
                }
            }
        }
        return max;
    }

NC38 螺旋矩阵

描述

给定一个m x n大小的矩阵(m行,n列),按螺旋的顺序返回矩阵中的所有元素。

数据范围:0≤n,m≤10,矩阵中任意元素都满足 0∣val∣≤100

要求:空间复杂度 O(nm) ,时间复杂度 O(nm)

输入 [[1,2,3],[4,5,6],[7,8,9]]    输出 [1,2,3,6,9,8,7,4,5]

思路

递归函数,设置上top下down左left右right四个值作为四个方向的边界,先从左向右输出(j=left,j<right避免和下一个方向的输出重复),再从上往下输出(i=top,i<down),从右往左输出(j=right,j>left),从下往上输出(i=down,i>top),然后将top++,down--,left++,right--传入递归

递归出口为left>right或者top>down

需要处理的一个特殊情况是当left==right或者top==down时说明是单列、单行,此时要将此列、行单独输出后返回。

代码

 public ArrayList<Integer> spiralOrder (int[][] matrix) {
        int m = matrix.length;
        ArrayList<Integer> res = new ArrayList<>();
        if(m==0) return res;
        int n = matrix[0].length;
        circle(matrix, 0, n-1, 0, m-1, res);
        return res;
    }
    public void circle(int[][] matrix, int left, int right, int top, int down,
                       ArrayList<Integer> res) {
        if(left>right || top>down) return;
        if(left==right){
            //只有一列按列遍历
            for(int i=top;i<=down;i++){
                 res.add(matrix[i][left]);
            }
            return;
        }
        if(top==down){
            //只有一行按行遍历
             for(int j=left;j<=right;j++){
                 res.add(matrix[down][j]);
            }
            return;
        }
        for(int j=left;j<right;j++){
            res.add(matrix[top][j]);
        }
        for(int i=top;i<down;i++){
            res.add(matrix[i][right]);
        }
        for(int j=right;j>left;j--){
             res.add(matrix[down][j]);
        }
        for(int i=down;i>top;i--){
            res.add(matrix[i][left]);
        }
        circle(matrix,left+1,right-1,top+1,down-1,res);
    }

NC17 最长回文子串

描述

对于长度为n的一个字符串A(仅包含数字,大小写英文字母),请设计一个高效算法,计算其中最长回文子串的长度。

数据范围: 1≤n≤1000

要求:空间复杂度 O(1),时间复杂度 O(n2)

进阶:  空间复杂度O(n),时间复杂度 O(n)

思路

暴力:i遍历字符串,index遍历从i到字符串尾,对每个子串s.subString(i,index+1),如果是回文串,记录长度,如果大于最大值覆盖。

另外需要一个判断是否为回文串的函数。

时间复杂度为O(N^2)+O(n)

代码

ublic int getLongestPalindrome (String A) {
        int maxLength = 1;
        int length = 1;
        for (int i = 0; i < A.length(); i++) {
            int index = i;
            length = 1;
            for (; index < A.length(); index++) {
                if (isPalindrome(A.substring(i, index+1))) {
                    length = A.substring(i, index+1).length();
                    if (length > maxLength) {
                        maxLength = length;
                    }
                }
            }
        }
        return maxLength;
    }
    public boolean isPalindrome(String A) {
        int length = A.length();
        Stack<Character> stack = new Stack<>();
        for (int i = 0; i < length; i++) {
            if (length % 2 != 0) {
                if (i < length / 2) {
                    stack.add(A.charAt(i));
                } else if (i > length / 2) {
                    if (stack.isEmpty() || !stack.pop().equals(A.charAt(i))) {
                        return false;
                    }
                }
            } else {
                if (i < length / 2) {
                    stack.add(A.charAt(i));
                } else if (i >= length / 2) {
                    if (stack.isEmpty() || !stack.pop().equals(A.charAt(i))) {
                        return false;
                    }
                }
            }
        }
        if (!stack.isEmpty()) return false;
        return true;
    }

NC54 三数之和

描述

给出一个有n个元素的数组S,S中是否有元素a,b,c满足a+b+c=0?找出数组S中所有满足条件的三元组。

数据范围:0≤n≤1000,数组中各个元素值满足 ∣val∣≤100

空间复杂度:O(n2),时间复杂度 O(n2)

注意:

  1. 三元组(a、b、c)中的元素必须按非降序排列。(即a≤b≤c)
  2. 解集中不能包含重复的三元组。
例如,给定的数组 S = {-10 0 10 20 -10 -40},解集为(-10, -10, 20),(-10, 0, 10) 

思路

先将数组排序(时间复杂度O(logn)),用i遍历数组。对每一个i进行循环:i右侧的第一个元素下标赋值给low,最后一个元素下标赋值给high,如果low和high所在位置数字相加等于-arr[i]将这三个元素加入到结果,同时low++,high--来继续寻找,如果小于则low++,如果大于则high--。该循环结束条件应为low不超过右边界(长度-1),high不超过左边界(0),且low<high。

除此之外要考虑去重的问题,可能有两种情况出现重复,

第一种是元素重复被选中:假设输入为[-40,-10,-10,0,10,20]的情况下,如果i遍历到第一个-10,下面去遍历会找到[-10,20][0,10],等到i遍历到第二个-10一样会得到相同的结果,这样就会产生重复。对这种重复的解决办法是,如果arr[i]==arr[i-1]就跳过(所以i需要从1开始)

第二种是进行双指针查找的区间里出现重复值:假设输入为[-10,-10,0,0,10,],i遍历到-10,找到[0,10],[0,10]两个重复。对这种的处理方式是如果arr[low]==arr[low-1]则跳过arr[low],如果arr[high]==arr[high+1]则跳过arr[high]。

为了防止数据越界,此处需要限制high<=arr.length-1,本来也应该限制low>=0,但是low的左边界是i不是0,所以此处应该是限制low>i+1(如果low取i+1,那么也会将arr[i]和arr[i+1]进行比较,arr[i]是我们选中的数,他允许和查找范围内的重复,所以显然不能取此值)

代码

public ArrayList<ArrayList<Integer>> threeSum (int[] num) {
        //先排序
        Arrays.sort(num);
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        for (int i = 0; i < num.length; i++) {
            if (i >= 1 && num[i] == num[i - 1]) {
                continue;
            }
            int low = i + 1;
            int high = num.length - 1;
            while (low <= num.length - 1 && high >= 1 && low < high) {
                if (low > i+1 && num[low] == num[low - 1]) {
                    low++;
                    continue;
                }
                if (high + 1 <= num.length - 1 && num[high] == num[high + 1]) {
                    high--;
                    continue;
                }
                if (num[low] + num[high] == 0 - num[i]) {
                    ArrayList<Integer> tempList = new ArrayList<Integer>();
                    tempList.add(num[i]);
                    tempList.add(num[low]);
                    tempList.add(num[high]);
                    result.add(new ArrayList<Integer>(tempList));
                    low++;
                    high--;
                } else if (num[low] + num[high] < 0 - num[i]) {
                    low++;
                } else {
                    high--;
                }
            }
        }
        return result;
    }

NC32 求平方根

描述

实现函数 int sqrt(int x).

计算并返回 x 的平方根(向下取整)

数据范围: 0<=x<231−1

要求:空间复杂度 O(1),时间复杂度 O(logx)

代码

public int sqrt (int x) {
        int low = 1;
        int high = x;
        if (x == 1) return 1;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (x / mid >= mid && x / (mid + 1) <= mid) {
                return mid;
            } else if (x / mid >= mid) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }
        return 0;
    }

NC91 最长上升子序列(三)

描述

给定数组 arr ,设长度为 n ,输出 arr 的最长上升子序列。(如果有多个答案,请输出其中 按数值(注:区别于按单个字符的ASCII码值)进行比较的 字典序最小的那个)

数据范围:0≤n≤200000,0≤arri​≤1000000000

要求:空间复杂度 O(n),时间复杂度 O(nlogn)

思路

输入 [2,1,5,3,6,4,8,9,7]

输出 [1,3,4,8,9]

dp[i]表示到arr[i]为止最长的子序列长度,初始化dp为[1,1,1,1,1,1,1,1,1],输入[2]时,最长长度为1,输入[2,1]因为左侧没有比1更大的,所以dp[1]=1,输入[2,1,5],从左往右挨个找比5小的,并且将dp[i]加一,取比5小的里面dp[i]加一最大的。就这样,每个元素都从左往右找所有比自己小的,并且找出其中长度最长的、加一。即dp[i]=Max(dp[j])+1 (j<i,arr[j]<arr[i])

可以得到动态规划数组为:

[1,1,2,2,3,3,4,5,4] 至此可以得出最大上升序列长度,但怎样求解结果序列呢?

可以看出结果其实是[1,1],[2,2],[3,3],[4],[5]里面分别找到原来值最小的(最大长度5已经出现了,它右侧的4就没意义了)

我们考虑两个dp值一样的序列,如果右面的数组值比左面数组值大,那么右面的dp值应该至少为左面的dp值加一才对,显然与现实情况不符,所以我们可以得到假设条件不正确,即右面的数组值应该小于左边的值。因此想找到每个分组里数组值最小的,我们只要得到每组里最右边的一个就可以了。

至此得到时间复杂度为O(n^2)的思路。代码如下:

public int[] LIS (int[] arr) {
        int[] dp = new int[arr.length];
        if (arr.length == 0) return dp;
        int maxLength = 0;
        int max=0;
        Arrays.fill(dp,1);
        for (int i = 0; i < arr.length; i++) {
            for(int j=0;j<i;j++){
                if(arr[j]<arr[i]){
                   dp[i] = Math.max(dp[j]+1,dp[i]);
                }
            }
            maxLength = Math.max(maxLength,dp[i]);
        }
        int[] res = new int[maxLength];
        int length = maxLength;
        for(int i=arr.length-1;i>=0 && length>0;i--){
            if(dp[i]==length){
                res[length-1] = arr[i];
                length--;
            }
        }
        return res;
    }

时间复杂度要求为O(nlog n),考虑用二分进行优化。

上面算法耗时多的就在于对每个a[i]都需要遍历i次,如果能够将当前最大长度和该长度对应的最小的数字记录下来,那a[i]就只需要和这个数比较就能知道dp[i]以及a[i]需不需要放到结果集里了。

所以考虑维护一个单调增长的序列tail。

输入 [2,1,5,3,6,4,8,9,7]

tail[] input[] dp[]

tail[2] input[2] dp[1]

tail[1] input[2,1] dp[1,1]

tail[1,5] input[2,1,5] dp[1,1,2]

tail[1,3] input[2,1,5,3] dp[1,1,2,2]

tail[1,3] input[2,1,5,3,6] dp[1,1,2,2,3] ...

可以看出规律为每次新增的数如果大于队尾,就直接加入到tail,tail长度加一,并且dp[i]=tail长度,如果每次新增的数字小于队尾,那就需要把从右到左第一个大于他的数取代掉,这个数的位置temp表示当前arrp[i]对应的递增序列结尾处,所以dp[i]=temp+1。这个寻找取代数的过程可以用二分,整体复杂度就降低为O(nlogn)了。

 public int[] LIS (int[] arr) {
        int[] dp = new int[arr.length];
        if (arr.length == 0) return dp;
        int maxLength = 0;
        int max = 0;
        Arrays.fill(dp, 1);
        int[] tail = new int[arr.length];
        //index表示tail内的元素个数
        int index = 0;
        for (int i = 0; i < arr.length; i++) {
            //tail里没有元素或者当前值大于队尾,直接加入
            if (i == 0 || arr[i] > tail[index - 1]) {
                tail[index] = arr[i];
                index++;
                dp[i] = index;
            } else {
                //替换,tail数组长度不变,dp数组长度加一
                int temp = findPlace(tail, arr[i], index);
                tail[temp] = arr[i];
                dp[i] = temp + 1;
            }
        }
        int[] res = new int[index];
        for (int i =arr.length- 1; i >= 0 && index > 0; i--) {
            if (dp[i] == index) {
                res[--index] = arr[i];
            }
        }
        return res;
    }
    public int findPlace(int[] tail, int key, int index) {
        int low = 0;
        int high = index - 1;
        while (low < high) {
            int mid = (low + high) / 2;
            if (tail[mid] < key) {
                low = mid + 1;
            } else {
                high = mid;
            }
        }
        return low;
    }

NC48 在旋转过的有序数组中寻找目标值

描述

有一个长度为 n 的按严格升序排列的整数数组 nums ,在实行 search 函数之前,在某个下标 k 上进行旋转,使数组变为[nums[k],nums[k+1],.....,nums[nums.length-1],nums[0],nums[1],.......,nums[k-1]]。

给定旋转后的数组 nums 和一个整型 target ,请你查找 target 是否存在于 nums 数组中并返回其下标(从0开始计数),如果不存在请返回-1。

数据范围:0≤n≤10000,0≤target≤100000
要求:空间复杂度 O(1) ,时间复杂度 O(n)

比如,数组[0,2,4,6,8,10]在下标3处旋转之后变为[6,8,10,0,2,4], 当给定target为10时,10的下标是2,target为3时,nums数组中不存在3,所以返回-1

思路

数组[0,2,4,6,8,10]在下标3处旋转之后变为[6,8,10,0,2,4] 可以看出旋转后数组的特点是k左侧为递增序列,k右侧也为递增序列,取mid为中间值。

如果num[mid]大于等于num[left]说明mid左侧为递增序列,mid可能为两个递增序列的分界线,也可能属于左侧递增序列的一部分。如中间值为8,左侧序列为6,8 右侧序列为10,0,2,4。如果num[mid]小于num[right]说明mid右侧为递增序列,mid可能为两个递增序列的分界线,也可能属于右侧递增序列的一部分,如中间值为2,左侧序列为6,8,10,0,右侧序列为4。

1.如果左侧为递增序列

(1)target>=num[mid],说明target不在mid左边,下一次取值区间为(mid+1,right)。

(2)如果target<num[mid],说明target可能在左边序列mid左侧那部分,也可能在右侧序列中。所以需要再判断target是否大于num[left],如果大于那么target就属于左侧序列的左边部分,下一次取值空间为(left,mid-1),如果小于,说明target应该在右侧序列中,下一次取值为(mid+1,right)。

2.如果右侧为递增序列

(1)target<=num[mid],说明target不在mid右边,下一次取值为(left,mid-1)。

(2)如果target>num[mid],说明可能在mid右侧也可能在左侧序列。判断target是否大于num[right],如果大于说明在左侧序列,下次取(left,mid-1),如果小于说明在mid右侧,取(mid+1,right)

代码

public int search (int[] nums, int target) {
        int left = 0;
        int right = nums.length - 1;
        while (left <= right) {
            int mid = left + (right - left) / 2;
            if (nums[mid] == target) return mid;
            //如果mid左侧数组有序
            if (nums[mid] >= nums[left]) {
                if (target > nums[mid]) {
                    left = mid + 1;
                } else {
                    if (target >= nums[left]) {
                        right = mid;
                    } else {
                        left = mid + 1;
                    }
                }
            }
            //如果mid右侧数组有序
            else if (nums[mid] < nums[right]) {
                if (target < nums[mid]) {
                    right = mid;
                } else {
                    if (target > nums[right]) {
                        right = mid;
                    } else {
                        left = mid + 1;
                    }
                }
            }
        }
        return -1;
    }

BM74 数字字符串转化成IP地址

描述

现在有一个只包含数字的字符串,将该字符串转化成IP地址的形式,返回所有可能的情况。

例如:

给出的字符串为"25525522135",

返回["255.255.22.135", "255.255.221.35"]. (顺序没有关系)

数据范围:字符串长度 0≤n≤12

要求:空间复杂度 O(n!),时间复杂度 O(n!)

注意:ip地址是由四段数字组成的数字序列,格式如 "x.x.x.x",其中 x 的范围应当是 [0,255]。

思路

2 5 5 2 5 5 2 2 1 3 5

0 1 2 3 4 5 6 7 8 9 10(长度为n=11)

将字符串变为ip地址,需要插入三个.。

用i表示第一个点插入的下标,那么i应该大于等于1(保证第第一段数字长度至少为1)且小于等于3(保证第一段数字长度至多为3),且i应该小于n-2(保证后面至少有三个数,有两个空留给剩下两个点)

用j表示第二个点插入的下标,那么i+1<=j<=i+3 && j<n-1

用k表示第三个点插入的下标,那么j+1<=k<=j+3&&k<n

以上为三层循环,在循环中判断:

字符串1 = s.subString(0,i)

字符串2 = s.subString(i,j)

字符串3 = s.subString(j)

如果最后一段字符串长度大于3 退出此次循环

这三个字符串转换为数字是否小于255 如果大于continue退出此次循环

如果三个字符串任意一个首数字为0且长度不为1 也不符合 同样退出此次循环

符合条件的 加入到结果集

代码

import java.util.*;
public class Solution {
    public ArrayList<String> restoreIpAddresses (String s) {
        ArrayList<String> res = new ArrayList<String>();
        int n = s.length();
        //遍历IP的点可能的位置(第一个点)
        for(int i = 1; i < 4 && i < n - 2; i++){ 
            //第二个点的位置
            for(int j = i + 1; j < i + 4 && j < n - 1; j++){ 
                //第三个点的位置
                for(int k = j + 1; k < j + 4 && k < n; k++){ 
                    //最后一段剩余数字不能超过3
                    if(n - k >= 4) 
                        continue; 
                    //从点的位置分段截取
                    String a = s.substring(0, i);
                    String b = s.substring(i, j);
                    String c = s.substring(j, k);
                    String d = s.substring(k);
                    //IP每个数字不大于255
                    if(Integer.parseInt(a) > 255 || Integer.parseInt(b) > 255 || Integer.parseInt(c) > 255 || Integer.parseInt(d) > 255)
                        continue;
                    //排除前导0的情况
                    if((a.length() != 1 && a.charAt(0) == '0') || (b.length() != 1 && b.charAt(0) == '0') ||  (c.length() != 1 && c.charAt(0) == '0') || (d.length() != 1 && d.charAt(0) == '0'))
                        continue;
                    //组装IP地址
                    String temp = a + "." + b + "." + c + "." + d; 
                    res.add(temp);
                }
            }
        }
        return res;
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值