面试Java笔试题精选解答

文章目录

    • 热身级别
      • 数组中重复的数字
        • 思路:使用map或HashSet来遍历一遍就可以找出重复的字符
        • 样例解答
      • 用两个栈实现队列
        • 思路:Stack1正向进入,队头在栈底,用于进队列操作;Stack2是Stack1倒栈形成,队头在栈顶,用于出队列操作。
        • 样例解答
      • 非递归实现斐波那契数列
        • 思路:循环来实现,2个变量保留前2个历史值。
        • 样例解答
      • log(n)复杂度查找旋转数组的最小数字
        • 思路:原来是排序数组,现队尾是原排序数组的中间值,二分查找并和现队尾比较来确定舍弃哪部分。
        • 样例解答
      • 调整数组顺序使奇数位于偶数前面
        • 首位双指针遍历交换奇偶
        • 样例解答
      • 连续子数组的最大和
        • 每当sum小于0就置0,并记录sum的最大值。
        • 样例解答
      • 第一个只出现一次的字符
        • 思路:使用Map记录出现次数。考虑到只有小写字母,也可使用字母表数组实现会更高效。
        • 样例解答
      • 在排序数组中查找数字出现的次数
        • 2次二分查找重复数字的前、后边界
        • 样例题解
      • 查找0~n-1中缺失的数字
        • 思路:二分查找,判断是否是目标的依据:该位置的值和该位置坐标是否相同
        • 样例解答
      • 和为s的两个数字
        • 双位指针。当和大了移动尾指针,当和小了移动首指针,当首指针>尾指针结束。
        • 样例题解
      • 和为s的连续正数序列
        • 思路:1)双循环枚举,第2个循环直接使用求和公式替代,根据求和公式反向求解可行的终点。2)滑动窗口双指针。初始时窗口为1-2,sum=3,当sum小于s时尾指针后移,当大于时首指针后移,当相等时记录并首指针后移。当首指针等于尾指针时结束。
        • 样例题解
      • 翻转单词顺序
        • 双指针倒序遍历。
        • 样例解答
      • 两数之和
        • 思路:使用Map存储,遍历同时查找Map是否存在所求之数
    • 实战级别
      • 数组中的逆序对
        • 思路:分治法,类似归并排序的做法,在合并2个有序数组时,计算逆序对。
        • 样例解答
      • 把数组排成最小的数
        • 利用字符串s1+s2和s2+s1的比较来确定s1和s2哪个比较小。
        • 样例解答
      • 无重复字符的最长子串
        • 思路:2个指针记录滑动窗口(表示当前最长不重复字符串)的首尾,不断移动尾指针,如果尾部值已经在滑动窗口内就移动首指针直至将其移出;使用map/HashSet存储滑动窗口内的值来加速查找;不断根据滑动窗口大小更新max值。
        • 样例解答
      • 寻找2个仅出现1次的数字
        • 思路:位运算求解。先求出异或和,从异或和找到某位为1的数,使用该数和数组每个数相异或来判断属于a或b分组,2个分组分别求异或和,得到2个解。
        • 样例解答
      • 寻找仅出现1次的数字
        • 1)位运算求解。统计所有数字中从低位到高位该位为1的数字的个数,如果个数不可以整除3,则所求在该位为1。2)状态机。
        • 样例解答待补全

热身级别

数组中重复的数字

题源:https://leetcode-cn.com/problems/shu-zu-zhong-zhong-fu-de-shu-zi-lcof/

思路:使用map或HashSet来遍历一遍就可以找出重复的字符
样例解答
class Solution {
    public int findRepeatDocument(int[] documents) {
        Map map = new HashMap();
        for(int i : documents){
            // 判断是否已经存在
            if(map.containsKey(i)){
                return i;
            }
            // 不存在则记录到map
            map.put(i, 1);
        }
        // 未找到
        return -1;
    }
}
class Solution {
    public int findRepeatDocument(int[] documents) {  
        Set set = new HashSet<>();  
        for(int i : documents){  
            // 如果添加失败,则说明已经存在该元素,直接返回  
            if(!set.add(i)){  
                return i;  
            }  
        }  
        // 未找到  
        return -1;  
    }  
}

用两个栈实现队列

题源:https://leetcode.cn/problems/yong-liang-ge-zhan-shi-xian-dui-lie-lcof/

思路:Stack1正向进入,队头在栈底,用于进队列操作;Stack2是Stack1倒栈形成,队头在栈顶,用于出队列操作。
样例解答
class CQueue {

    // 操作push元素
    private Stack stack1;
    // 操作pop元素
    private Stack stack2;

    public CQueue() {

        stack1 = new Stack<>();
        stack2 = new Stack<>();

    }
    
    public void appendTail(int value) {

        // 操作stack1
        stack1.push(value);

    }
    
    public int deleteHead() {

        // 操作stack2
        if(!stack2.isEmpty()){
            return stack2.pop();
        }

        // stack2为空,需要进行倒栈
        while(!stack1.isEmpty()){
            stack2.push(stack1.pop());
        }

        // 再次操作stack2
        if(!stack2.isEmpty()){
            return stack2.pop();
        }

        // 为空的情况
        return -1;

    }
}

非递归实现斐波那契数列

题源:https://leetcode.cn/problems/fei-bo-na-qi-shu-lie-lcof/description/

思路:循环来实现,2个变量保留前2个历史值。
样例解答
class Solution {
    public int fib(int n) {
        if(n<=1){
            return n;
        }

        // n-2值
        int p2=0;
        // n-1值
        int p1=1;
        for(int i=2;i<=n;i++){
            // int t = (p1+p2)%1000000007;
            int t = p1+p2;
            p2=p1;
            p1=t;
        }
        return p1;
    }
}

log(n)复杂度查找旋转数组的最小数字

题源:https://leetcode.cn/problems/xuan-zhuan-shu-zu-de-zui-xiao-shu-zi-lcof/description/

思路:原来是排序数组,现队尾是原排序数组的中间值,二分查找并和现队尾比较来确定舍弃哪部分。
样例解答
class Solution {
    public int stockManagement(int[] stock) {
        int low = 0;
        int high = stock.length - 1;
        
        while(low stock[high]){
                // mid在原队列的分割点后面,现在变为现队列的前部(原队列头部现在在其后面),故舍去其前的部分;且mid比high点值大,肯定非最小值,故low可取mid+1,避免low+1=high时死循环
                low = mid + 1;
            } else if(stock[mid] < stock[high]) {
                // mid在原队列的分割点前面,现在变为现队列的后部(原队列头部现在在其前面),故舍去其后部分
                high = mid;
            } else {
                // mid和high位置的值相同,则可能在前,也可能在后,故前后部分都不能舍弃,仅舍去high这个边界点
                high = high - 1;
            }
        }

        return stock[low];

    }
}

调整数组顺序使奇数位于偶数前面

题源:https://leetcode.cn/problems/diao-zheng-shu-zu-shun-xu-shi-qi-shu-wei-yu-ou-shu-qian-mian-lcof/

首位双指针遍历交换奇偶
样例解答
class Solution {
    public int[] trainingPlan(int[] actions) {
        int head = 0;
        int tail = actions.length - 1;
        while(true){
            // 从头部开始寻找偶数
            while(head

连续子数组的最大和

题源:https://leetcode.cn/problems/lian-xu-zi-shu-zu-de-zui-da-he-lcof/

每当sum小于0就置0,并记录sum的最大值。
样例解答
class Solution {
    public int maxSales(int[] sales) {
        int max = sales[0];
        int pre = sales[0];
        for(int i = 1;i0)
            // max[i] = 0+sales[i];(max[i-1]<=0)
            int cur=pre>0?pre+sales[i]:sales[i];
            if(cur>max){
                max = cur;
            }
            pre=cur;
        }
        return max;
    }
}

第一个只出现一次的字符

题源:https://leetcode.cn/problems/di-yi-ge-zhi-chu-xian-yi-ci-de-zi-fu-lcof/description/

思路:使用Map记录出现次数。考虑到只有小写字母,也可使用字母表数组实现会更高效。
样例解答
class Solution {
    public char dismantlingAction(String arr) {
        Map map = new HashMap<>(arr.length());
        // 循环一遍,记录是否重复字符
        for(char c : arr.toCharArray()){
            if(map.containsKey(c)){
                map.put(c, false);
            } else {
                map.put(c, true);
            }
        }
        // 循环一遍,寻找第一个不重复字符
        for(char c : arr.toCharArray()){
            if(map.get(c)){
                return c;
            }
        }
        // 不存在不重复字符
        return ' ';
    }
}
class Solution {
    public char dismantlingAction(String arr) {
        // 代表了26个字母表的数组
        byte[] charArr = new byte[26];
        char[] sc = arr.toCharArray();
        byte init = 0;
        byte one = 1;
        byte more = 2;
        for(char c : sc){
            // 标记对应字母表位置的记号
            charArr[c-'a']=charArr[c-'a']==init?one:more;
        }
        for(char c : sc){
            // 寻找第一个非重复的字母
            if(charArr[c-'a']==one){
                return c;
            }
        }
        return ' ';
    }
}

在排序数组中查找数字出现的次数

题源:https://leetcode.cn/problems/zai-pai-xu-shu-zu-zhong-cha-zhao-shu-zi-lcof/description/

2次二分查找重复数字的前、后边界
样例题解
class Solution {
    public int countTarget(int[] scores, int target) {
        // 二分查找
        int low = 0;
        int high = scores.length - 1;
        int targetIndex = -1;
        while(low <= high){
            int mid = (low + high)/2;
            if(scores[mid] == target){
                // 找到目标,直接退出
                targetIndex = mid;
                break;
            }
            if(scores[mid] < target){
                // 目标在mid右侧,舍去mid左侧
                low = mid + 1;
            } else {
                // 目标在mid左侧,舍去mid右侧
                high = mid - 1;
            }
        }
        // 未找到目标
        if(targetIndex == -1){
            return 0;
        }
        // 进一步二分查找左边界
        int tLow = low;
        int tHigh = targetIndex;
        int leftIndex = -1;
        while(true){ // 左边界一定存在,故死循环即可
            int mid = (tLow + tHigh)/2;
            if(scores[mid] == target){
                if(mid==tLow || scores[mid-1]target){
                    // 找到右边界
                    rightIndex = mid;
                    break;
                }
                // 非右边界,目标在mid右侧,舍去mid左侧
                tLow = mid + 1;
            } else {
                // 只剩target[mid]>target的情况,此时,目标在mid左侧,舍去mid右侧
                tHigh = mid - 1;
            }
        }

        return rightIndex - leftIndex + 1;
    }
}

查找0~n-1中缺失的数字

题源:https://leetcode.cn/problems/que-shi-de-shu-zi-lcof/

思路:二分查找,判断是否是目标的依据:该位置的值和该位置坐标是否相同
样例解答
class Solution {
    public int takeAttendance(int[] records) {
        int low = 0;
        int high = records.length - 1;
        while(low <= high){
            int mid = (low + high)/2;
            if(records[mid] != mid){
                // 判断是否是分界点
                if(mid==0 || records[mid-1] == mid-1){
                    return mid;
                }
                // 分界点在mid左侧,舍去mid右侧部分
                high = mid - 1;
            } else {
                // 分界点在mid右侧,舍弃mid左侧部分
                low = mid + 1;
            }
        }
        // 没有找到,返回最后1个数,即数组长度
        return records.length;
    }
}

和为s的两个数字

题源:https://leetcode.cn/problems/he-wei-sde-liang-ge-shu-zi-lcof/description/

双位指针。当和大了移动尾指针,当和小了移动首指针,当首指针>尾指针结束。
样例题解
class Solution {
    public int[] twoSum(int[] price, int target) {
        int head = 0;
        int tail = price.length-1;
        while(head<=tail){
            if(price[head] + price[tail] == target){
                // 找到目标
                return new int[]{price[head], price[tail]};
            }
            if(price[head] + price[tail] > target){
                // 和偏大,前移尾指针
                tail--;
            } else {
                // 和偏小,后移头指针
                head++;
            }
        }

        // 未找到
        return new int[0];
    }
}

和为s的连续正数序列

题源:https://leetcode.cn/problems/he-wei-sde-lian-xu-zheng-shu-xu-lie-lcof/description/

思路:1)双循环枚举,第2个循环直接使用求和公式替代,根据求和公式反向求解可行的终点。2)滑动窗口双指针。初始时窗口为1-2,sum=3,当sum小于s时尾指针后移,当大于时首指针后移,当相等时记录并首指针后移。当首指针等于尾指针时结束。
样例题解
class Solution {
    public int[][] fileCombination(int target) {
        int head = 1;
        int tail = 2;
        List> rst = new ArrayList();
        while(head one = new ArrayList();
                for(int i=head;i<=tail;i++){
                    one.add(i);
                }
                rst.add(one);

                head++;
            }
            if(sum>target){
                // 和偏大,后移head指针
                head++;
            } else {
                // 和偏小,后移tail指针
                tail++;
            }
        }

        // 组装答案
        int[][] rstArr = new int[rst.size()][];
        int i = 0;
        for(List one : rst){
            int[] oneArr = one.stream().mapToInt(Integer::intValue).toArray();
            rstArr[i++] = oneArr;
        }

        return rstArr;
    }
}

翻转单词顺序

题源:https://leetcode.cn/problems/fan-zhuan-dan-ci-shun-xu-lcof/description/

双指针倒序遍历。
样例解答
class Solution {
    public String reverseMessage(String message) {
        int start = message.length() - 1;
        int end = message.length() - 1;
        StringBuilder sb = new StringBuilder();
        while(true){
            // 先end指针寻找单词结尾
            while(end>=0 && message.charAt(end) == ' '){
                end--;
            }
            if(end<0){
                // 未找到,结束
                return sb.length()>0?sb.deleteCharAt(sb.length()-1).toString():"";
            }
            // 再start指针寻找单词开始
            start = end;
            while(start>=0 && message.charAt(start) != ' '){
                start--;
            }
            // 记录单词,即[start-1,end]
            sb.append(message.substring(start+1, end+1));
            sb.append(' ');
            // 将end移动到下一次寻找为止
            end = start;
        }
    }
}

两数之和

题源:https://leetcode.cn/problems/two-sum/description/

思路:使用Map存储,遍历同时查找Map是否存在所求之数
class Solution {
    public int[] twoSum(int[] nums, int target) {
        Map map = new HashMap<>();
        // 循环时边查询是否存在匹配的,边插入
        for(int i=0;i

实战级别

数组中的逆序对

题源:https://leetcode.cn/problems/shu-zu-zhong-de-ni-xu-dui-lcof/

思路:分治法,类似归并排序的做法,在合并2个有序数组时,计算逆序对。
样例解答
class Solution {

    // 逆序计数
    private int count;

    public int reversePairs(int[] record) {
        count = 0;
        sortMerge(record);
        return count;
    }

    private int[] sortMerge(int[] record){
        int len = record.length;
        // 出口
        if(len <= 1){
            return record;
        }
        // 拆分2部分分别进行归并排序
        int mid = len/2;
        int[] sorted1 = sortMerge(Arrays.copyOfRange(record, 0, mid));
        int[] sorted2 = sortMerge(Arrays.copyOfRange(record, mid, len));

        // 逆序合并2个有序数组
        int i = 0;
        int j = 0;
        int cur = 0;
        int[] merged = new int[len];
        while(isorted2[j]){
                // 在逆序合并中,当前情况说明sorted1当前元素比sorted2中剩余的所有元素都大,而sorted1在原数组中位于sorted2前面,故sorted2中剩余的元素个数即为逆序对个数
                count += len - mid - j;
                merged[cur++] = sorted1[i++];
            } else {
                merged[cur++] = sorted2[j++];
            }
        }
        // 处理剩余的部分
        while(i

把数组排成最小的数

题源:https://leetcode.cn/problems/ba-shu-zu-pai-cheng-zui-xiao-de-shu-lcof/

利用字符串s1+s2和s2+s1的比较来确定s1和s2哪个比较小。
样例解答
class Solution {
    public String crackPassword(int[] password) {
        List list = new ArrayList<>();
        // 将数字转换为字符串
        for(int i : password){
            list.add(String.valueOf(i));
        }
        // 对字符串列表排序,然后拼接为字符串
        // 不使用字符串自然序比较,是因为类似3和30这种情况下会出错。
        return list.stream().sorted((s1, s2)->(s1+s2).compareTo(s2+s1)).collect(Collectors.joining(""));
    }
}

无重复字符的最长子串

题源:https://leetcode.cn/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/description/

思路:2个指针记录滑动窗口(表示当前最长不重复字符串)的首尾,不断移动尾指针,如果尾部值已经在滑动窗口内就移动首指针直至将其移出;使用map/HashSet存储滑动窗口内的值来加速查找;不断根据滑动窗口大小更新max值。
样例解答
class Solution {
    public int dismantlingAction(String arr) {
        if(arr.length() <= 1){
            return arr.length();
        }
        int max = 1;
        Set set = new HashSet<>();
        int head = 0;
        int tail = 1;
        set.add(arr.charAt(0));
        while(tail < arr.length()){
            // 判断当前tail是否存在于当前窗口中
            if(!set.add(arr.charAt(tail))){
                // 添加失败,说明当前值已经在窗口中存在。需要将head前移,直至该值移出窗口
                // 先统计当前窗口的长度,更新max记录
                if(set.size()>max){
                    max = set.size();
                }
                while(arr.charAt(head) != arr.charAt(tail)){
                    set.remove(arr.charAt(head));
                    head++;
                }
                head++;
            }
            // 继续扩大窗口
            tail++;
        }
        // 统计当前窗口的长度,更新max记录
        if(set.size()>max){
            max = set.size();
        }
        return max;
    }
}
class Solution {
    public int lengthOfLongestSubstring(String s) {
        if(s.length() <= 1){
            return s.length();
        }
        int max = 1;
        // map存储元素和其下标,不做删除,故需要和窗口的左边界比较判断是否在窗口内
        Map map = new HashMap<>();
        int head = 0;
        int tail = 1;
        map.put(s.charAt(0), 0);
        while(tail < s.length()){
            // 判断当前tail是否存在于当前窗口中
            if(map.containsKey(s.charAt(tail)) && map.get(s.charAt(tail))>=head){
                // 当前值已经在窗口中存在。需要将head前移至重复的元素的下一个位置
                // 先统计当前窗口的长度,更新max记录
                if(tail-head>max){
                    max = tail-head;
                }
                head=map.get(s.charAt(tail))+1;
                // 更新重复元素的新坐标
                map.put(s.charAt(tail), tail);
            } else {
                // 添加元素
                map.put(s.charAt(tail), tail);
            }
            // 继续扩大窗口
            tail++;
        }
        // 统计当前窗口的长度,更新max记录
        if(tail-head>max){
            max = tail-head;
        }
        return max;
    }
}
// 动态规划方法
class Solution {
    public int dismantlingAction(String arr) {
        if(arr.length() <= 1){
            return arr.length();
        }
        int max = 1;
        // 动态规划
        // 设max[i]表示以i位置结尾的最长不重复字符串,则:
        // max[0]=第一个字符;
        // max[i]=max[i-1]+当前字符;(max[i-1]不包含当前字符)
        // max[i]=max[i-1]中当前字符之后的部分+当前字符;(max[i-1]包含当前字符)
        // 实际不需要保存max[],只需要保存前值即可
        String pre = arr.substring(0,1);
        for(int i=1;i max){
                max = pre.length();
            }
        }

        return max;

    }
}

寻找2个仅出现1次的数字

题源:https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-lcof/description/

思路:位运算求解。先求出异或和,从异或和找到某位为1的数,使用该数和数组每个数相异或来判断属于a或b分组,2个分组分别求异或和,得到2个解。
样例解答
class Solution {
    public int[] sockCollocation(int[] sockets) {
        // 先对所有值进行异或
        int s = 0;
        for(int i : sockets){
            s=s^i;
        }
        // 对s的2进制寻找1个为1的位,该位为1说明2个目标数字在该位的值不同,故依据该位对所有值分为2组,2个目标数字必然各在1个组内1
        int bit = 0x1;
        while((s & bit)==0){
            bit = bit << 1;
        }
        // bit即对应位为1,使用其来进行分组
        int group1 = 0;
        int group2 = 0;
        for(int i : sockets){
            if((i & bit) == 0){
                // 对应位为0,分到1组,直接进行累加异或
                group1=group1^i;
            } else {
                // 对应位为1,分到2组,直接进行累加异或
                group2=group2^i;
            }
        }
        // 根据异或性质,累加异或后,group1和group2就只剩不同的值了,即目标数字
        return new int[]{group1, group2};
    }
}

寻找仅出现1次的数字

题源:https://leetcode.cn/problems/shu-zu-zhong-shu-zi-chu-xian-de-ci-shu-ii-lcof/description/

1)位运算求解。统计所有数字中从低位到高位该位为1的数字的个数,如果个数不可以整除3,则所求在该位为1。2)状态机。
样例解答待补全

你可能感兴趣的:(职业发展,面试,java,算法)