面试Java笔试题精选解答

文章目录

热身级别

数组中重复的数字

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

思路:使用map或HashSet来遍历一遍就可以找出重复的字符
样例解答
复制代码
class Solution {
    public int findRepeatDocument(int[] documents) {
        Map<Integer, Integer> 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<Integer> 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<Integer> stack1;
    // 操作pop元素
    private Stack<Integer> 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<high){
            // 现在队列的队尾都是原队列的分割点,根据和其比较来判断mid的位置(不使用队头是因为要考虑现队列为升序情况)
            int mid = (low + high)/2;
            if(stock[mid] > 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<tail && actions[head]%2!=0)head++;
            // 从尾部开始寻找奇数
            while(head<tail && actions[tail]%2!=1)tail--;
            // 交换找到的奇数偶数
            if(head<tail){
                int t = actions[head];
                actions[head] = actions[tail];
                actions[tail] = t;
            } else {
                // head和tail相遇,出口
                return actions;
            }
        }
    }
}

连续子数组的最大和

题源: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;i<sales.length;i++){
            // 动态规划,实际不需要保存max[],只需要保存前值
            // max[i]表示以i位置结尾的连续子数组最大和,则:
            // max[0]=sales[0]
            // max[i] = max[i-1]+sales[i];(max[i-1]>0)
            // 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<Character, Boolean> 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){
                    // 找到左边界
                    leftIndex = mid;
                    break;
                }
                // 非左边界,目标在mid左侧,舍去mid右侧
                tHigh = mid - 1;
            } else {
                // 只剩target[mid]<target的情况,此时,目标在mid右侧,舍去mid左侧
                tLow = mid + 1;
            }
        }
        // 进一步二分查找右边界
        tLow = targetIndex;
        tHigh = high;
        int rightIndex = -1;
        while(true){ // 右边界一定存在,故死循环即可
            int mid = (tLow + tHigh)/2;
            if(scores[mid] == target){
                if(mid==tHigh || 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<List<Integer>> rst = new ArrayList();
        while(head<tail){
            // 计算head至tail的值
            int sum=(head+tail)*(tail-head+1)/2;
            if(sum==target){
                // 找到一个目标,记录并后移head指针
                List<Integer> 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<Integer> 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<Integer, Integer> map = new HashMap<>();
        // 循环时边查询是否存在匹配的,边插入
        for(int i=0;i<nums.length;i++){
            if(map.containsKey(target-nums[i])){
                // 查询到,结束
                return new int[]{i, map.get(target-nums[i])};
            }
            map.put(nums[i], i);
        }
        return new int[0];
    }
}

实战级别

数组中的逆序对

题源: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(i<mid && j<len-mid){
            if(sorted1[i]>sorted2[j]){
                // 在逆序合并中,当前情况说明sorted1当前元素比sorted2中剩余的所有元素都大,而sorted1在原数组中位于sorted2前面,故sorted2中剩余的元素个数即为逆序对个数
                count += len - mid - j;
                merged[cur++] = sorted1[i++];
            } else {
                merged[cur++] = sorted2[j++];
            }
        }
        // 处理剩余的部分
        while(i<mid){
            merged[cur++] = sorted1[i++];
        }
        while(j<len-mid){
            merged[cur++] = sorted2[j++];
        }

        return merged;
        
    }
}

把数组排成最小的数

题源: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<String> 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<Character> 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<Character, Integer> 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<arr.length();i++){
            int index = pre.indexOf(arr.charAt(i));
            if(index==-1){
                pre=pre+arr.charAt(i);
            } else {
                pre=pre.substring(index+1)+arr.charAt(i);
            }
            // 统计pre长度,更新max
            if(pre.length() > 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)状态机。
样例解答待补全
相关推荐
晴殇i1 小时前
揭秘JavaScript中那些“不冒泡”的DOM事件
前端·javascript·面试
孟陬1 小时前
国外技术周刊 #1:Paul Graham 重新分享最受欢迎的文章《创作者的品味》、本周被划线最多 YouTube《如何在 19 分钟内学会 AI》、为何我不
java·前端·后端
想用offer打牌1 小时前
一站式了解四种限流算法
java·后端·go
绝无仅有1 小时前
Redis过期删除与内存淘汰策略详解
后端·面试·架构
绝无仅有1 小时前
Redis大Key问题排查与解决方案全解析
后端·面试·架构
华仔啊2 小时前
Java 开发千万别给布尔变量加 is 前缀!很容易背锅
java
AAA梅狸猫2 小时前
Looper.loop() 循环机制
面试
AAA梅狸猫2 小时前
Handler基本概念
面试
也些宝3 小时前
Java单例模式:饿汉、懒汉、DCL三种实现及最佳实践
java