剑指 Offer 38. 字符串的排列:https://leetcode-cn.com/problems/zi-fu-chuan-de-pai-lie-lcof/

点击查看代码
// 思想:从剩余可选字符中填充指定位置,直到所有位置填充完成
// 注意String和char[]互转;ArrayList转数组均通过toArray(new T[0])完成
public class Solution {
    public String[] permutation(String s) {
        // 标记该元素是否已使用,剪枝
        boolean[] used = new boolean[s.length()];
        // 用于结果去重
        HashSet<String> resSet = new HashSet<>();
        dfs("", s, resSet, used);

        ArrayList<String> res = new ArrayList<>(resSet);
        return res.toArray(new String[0]);
    }

    private void dfs(String curStr, String s, HashSet<String> set, boolean[] used) {
        if (curStr.length() == s.length()) {
            set.add(curStr);
        }
        // 填充下一位置的元素
        for (int i = 0; i < s.length(); i++) {
            if (used[i]) {
                // 如果该元素已经使用了,则继续查找下一个未使用的元素
                continue;
            }
            used[i] = true;
            dfs(curStr + s.charAt(i), s, set, used);
            used[i] = false;
        }
    }
}

剑指 Offer 48. 最长不含重复字符的子字符串:https://leetcode-cn.com/problems/zui-chang-bu-han-zhong-fu-zi-fu-de-zi-zi-fu-chuan-lcof/

点击查看代码
// 最长不含重复字符的子字符串:双指针 + hashmap
public class Solution {
    public static int lengthOfLongestSubstring(String s) {
        //k-v = 字符 - 字符所在位置。如果存在重复字符,更新位置到最新
        HashMap<Character, Integer> map = new HashMap<>();
        //非重复字符串长度 = r - l, l维护的是非重复区间的左边界(l, r]
        int l = -1;
        int maxLen = 0;
        for (int r = 0; r < s.length(); r++) {
            if (map.containsKey(s.charAt(r))) {
                // 更新左指针, 取max是为了避免abba这种样例场景下l的倒退
                l = Math.max(l, map.get(s.charAt(r)));
            }
            maxLen = Math.max(maxLen, r - l);
            map.put(s.charAt(r), r);
        }
        return maxLen;
    }
}
  1. 颠倒字符串中的单词:https://leetcode-cn.com/problems/reverse-words-in-a-string/
点击查看代码
// 正则表达式实现,忽略题目O(1)的空间复杂度限制
public class Solution {
    public String reverseWords(String s) {
        // 去掉空格,个数不限 "[\\s]+", 分割的字符串数组中存在""情况
        // 去掉逗号,个数不限 "[,]+", 分割的字符串数组中存在""情况
        String[] strArr = s.split("[\\s]+");
        // 题目已保证字符串种至少存在一个单词,StringBuilder非线程安全&性能更佳
        StringBuilder builder = new StringBuilder();
        int i = strArr.length - 1;
        while (i >= 0) {
            if (strArr[i].length() > 0) {
                if (builder.length() == 0) {
                    builder.append(strArr[i]);
                } else {
                    builder.append(" ").append(strArr[i]);
                }
            }
            i--;
        }

        return builder.toString();
    }
}

// 题目限制了空间复杂度为O(1):使用subString,用r和l保存单个单词的取值区间
public class Solution {
    public String reverseWords(String s) {
        StringBuilder builder = new StringBuilder();
        int r = s.length() - 1;
        while (r >= 0) {
            // r指向单次最右边的字母
            while (r >= 0 && s.charAt(r) == ' ') {
                r--;
            }
            if (r < 0) {
                break;
            }
            // l指向单次最左边的字母
            int l = r - 1;
            while (l >= 0 && s.charAt(l) != ' ') {
                l--;
            }
            if (l < 0 && s.charAt(0) == ' ') {
                break;
            }
            l++;

            // 基于l和r截取s的子串,[l, r)
            String subStr = s.substring(l, r + 1);
            if (builder.length() == 0) {
                builder.append(subStr);
            } else {
                builder.append(" ").append(subStr);
            }

            // 更新r
            r = l - 1;
        }
        return builder.toString();
    }
}

剑指 Offer 62. 圆圈中最后剩下的数字:https://leetcode-cn.com/problems/yuan-quan-zhong-zui-hou-sheng-xia-de-shu-zi-lcof/

点击查看代码
// 通用解法:使用队列模拟实现,但m很大时模拟实现会超时
public class Solution {
    public int lastRemaining(int n, int m) {
        LinkedList<Integer> queue = new LinkedList<>();
        for (int i = 0; i < n; i++) {
            queue.add(i);
        }

        while (queue.size() > 1) {
            int i = 1;
            // 循环淘汰第m个数字
            while (i < m) {
                queue.add(queue.poll());
                i++;
            }
            queue.poll();
        }
        return queue.poll();
    }
}

// 数学推导,定义:f(n, m)表示有n个人时最后存活下来的人的编号
// 则f(n, m) = (f(n-1, m) + m) % n, 且f(1,m) = 0
public class Solution {
    public int lastRemaining(int n, int m) {
        // 最后存活下来的人的编号, n == 1
        int pos = 0;
        // 基于公式,推导初始化状态下的存活下来的人的编号n > 1
        for (int i = 2; i <= n; i++) {
            pos = (pos + m) % i;
        }
        return pos;
    }
}
  1. 用 Rand7() 实现 Rand10():https://leetcode-cn.com/problems/implement-rand10-using-rand7/
点击查看代码
// 利用rand7可产生49个随机数,基于7进制,数据取值范围是[8, 56]
// 要生成[1,10]范围随机数,每4个数对应1~10中的一个数,则需要40个数
// 因此:rand7产生的数中,我们需要的数据范围是[8,47],总共40个数
public class Solution extends SolBase {
    public int rand10() {
        // 利用rand7生成[8,47]范围中的数
        int val = 48;
        while (val > 47) {
            val = (rand7() * 7 + rand7());
        }
        // 基于val构造rand10范围的数[1,10]
        return (val - 8) / 4 + 1;
    }
}
  1. x 的平方根:https://leetcode-cn.com/problems/sqrtx/
点击查看代码
// x取值范围大,必须使用二分查找,时间复杂度:O(logX)
public class Solution {
    public int mySqrt(int x) {
        int l = 0;
        int r = x;
        int ans = -1;
        while (l <= r) {
            int mid = (l + r) / 2;
            if ((long) mid * mid <= x) {
                ans = mid;
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        return ans;
    }
}

// 写一个函数,求带精度的平方根,函数参数为目标数字和精度。二分法
public class Solution {
    public float mySqrt(float n, float e) {
        float l = 0;
        float r = n;
        while (l <= r) {
            float mid = (1 + r) / 2;
            if (mid * mid < (n - e)) {
                l = mid + 1;
            } else if (mid * mid > (n + e)) {
                r = mid - 1;
            } else {
                return mid;
            }
        }
        return l;
    }
}
  1. 加油站:https://leetcode-cn.com/problems/gas-station/
点击查看代码
// 贪心:总加油量 >= 总耗油量一定有解;[0, i]如果位置i的剩余油量<0,则[0, i]中的任一位置都不能作为起点,枚举位置直接是[i+1]
public class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        int curTank = 0;
        int sumTank = 0;
        int res = 0;
        for (int i = 0; i < gas.length; i++) {
            curTank = curTank + gas[i] - cost[i];
            sumTank = sumTank + gas[i] - cost[i];
            if (curTank < 0) {
                // 重新以i + 1作为起点环行
                curTank = 0;
                res = i + 1;
            }
        }
        return sumTank >= 0 ? res : -1;
    }
}
  1. 合并区间:https://leetcode-cn.com/problems/merge-intervals/
点击查看代码
// 合并区间:数组排序 + 双指针
public class Solution {
    public int[][] merge(int[][] intervals) {
        if (intervals.length <= 1) {
            return intervals;
        }
        // 数组排序,元素1相同时再基于元素2做升序排序
        Arrays.sort(intervals, ((o1, o2) -> o1[0] == o2[0] ? o1[1] - o2[1] : o1[0] - o2[0]));
        ArrayList<int[]> res = new ArrayList<>();
        int s = intervals[0][0];
        int e = intervals[0][1];
        for (int i = 1; i < intervals.length; i++) {
            if (intervals[i][0] > e) {
                // 下一区间和当前维护的区间不相交,将当前区间加入res,同时更新s、e
                res.add(new int[]{s, e});
                s = intervals[i][0];
            }
            // 更新e, 待合并区间可能完全包含于当前维护的区间
            e = Math.max(e, intervals[i][1]);
        }
        // 判断最后维护的[s, e]是否已加入res
        if (res.size() == 0 || res.get(res.size() - 1)[1] < e) {
            res.add(new int[]{s, e});
        }

        return res.toArray(new int[res.size()][]);
    }
}
  1. 螺旋矩阵 II:https://leetcode-cn.com/problems/spiral-matrix-ii/
点击查看代码
    public int[][] generateMatrix(int n) {
        int[][] res = new int[n][n];
        int rlow = 0, rhigh = n - 1;
        int clow = 0, chigh = n - 1;

        int fillNum = 1, upNum = n * n;
        while (fillNum <= upNum) {
            // 从左到右
            for (int i = clow; i <= chigh; i++) {
                res[rlow][i] = fillNum++;
            }
            rlow++;
            // 从上到下
            for (int i = rlow; i <= rhigh; i++) {
                res[i][chigh] = fillNum++;
            }
            chigh--;
            // 从右到左
            for (int i = chigh; i >= clow; i--) {
                res[rhigh][i] = fillNum++;
            }
            rhigh--;
            // 从下到上
            for (int i = rhigh; i >= rlow; i--) {
                res[i][clow] = fillNum++;
            }
            clow++;
        }
        return res;
    }
}
  1. 罗马数字转整数:https://leetcode-cn.com/problems/roman-to-integer/
点击查看代码
 public int romanToInt(String s) {
        // 使用hashmap存储字符到数字的映射,直接累加转换后的数字即可
        HashMap<String, Integer> map = new HashMap<>();
        String[] keys = {"I", "V", "X", "L", "C", "D", "M", "IV", "IX", "XL", "XC", "CD", "CM"};
        int[] values = {1, 5, 10, 50, 100, 500, 1000, 4, 9, 40, 90, 400, 900};
        for (int i = 0; i < keys.length; i++) {
            map.put(keys[i], values[i]);
        }

        int sum = 0;
        for (int i = 0; i < s.length(); i++) {
            if (i + 1 == s.length() || s.charAt(i) == 'V' || s.charAt(i) == 'L' || s.charAt(i) == 'D' || s.charAt(i) == 'M') {
                sum += map.get(String.valueOf(s.charAt(i)));
            } else {
                // 标记本此取值是否已完成处理
                if (s.charAt(i) == 'I' && (s.charAt(i + 1) != 'V' && s.charAt(i + 1) != 'X')) {
                    sum += 1;
                } else if (s.charAt(i) == 'X' && (s.charAt(i + 1) != 'L' && s.charAt(i + 1) != 'C')) {
                    sum += 10;
                } else if (s.charAt(i) == 'C' && (s.charAt(i + 1) != 'D' && s.charAt(i + 1) != 'M')) {
                    sum += 100;
                } else {
                    sum += map.get(String.valueOf(s.substring(i, i + 2)));
                    i++;
                }
            }
        }
        return sum;
    }
}
  1. 按序打印:https://leetcode-cn.com/problems/print-in-order/
点击查看代码
// 按序打印:CountDownLatch
// CountDownLatch-倒计时计算器: 同步工具类,用来协调多个线程之间的同步。核心方法:countDown/await。
//      缺点是其值只能被初始化一次,使用完毕后不能再次被使用
public class Foo {
    CountDownLatch cdLatch1, cdLatch2;

    public Foo() {
        // 用于线程1和线程2之间的同步
        cdLatch1 = new CountDownLatch(1);
        // 用于线程2和线程3之间的同步
        cdLatch2 = new CountDownLatch(1);
    }

    public void first(Runnable printFirst) throws InterruptedException {
        printFirst.run();
        cdLatch1.countDown();
    }

    public void second(Runnable printSecond) throws InterruptedException {
        // 等待cdLatch1计算器取值为0时被唤醒,在计数器为0之前会一致等待直到线程被中断或超出了指定的等待时间
        cdLatch1.await();
        printSecond.run();
        cdLatch2.countDown();
    }

    public void third(Runnable printThird) throws InterruptedException {
        // 等待cdLatch2计算器取值为0时被唤醒
        cdLatch2.await();
        printThird.run();
    }
}

// 三个线程循环打印ABC 10次,使用:ReentrantLock-可重入的互斥锁:lock/unlock,通过newCondition可实现线程之间的协调通信
public class Solution {
    private static int state = 0;
    private static final ReentrantLock lock = new ReentrantLock();
    private static final Condition condA = lock.newCondition();
    private static final Condition condB = lock.newCondition();
    private static final Condition condC = lock.newCondition();

    private static void function(String s, int targetId, Condition awaitCond, Condition signalCond) {
        while (state < 30) {
            // 尝试获取可重入的互斥锁
            lock.lock();

            try {
                if (state % 3 != targetId) {
                    // 当前线程释放锁,并将自己沉睡,等待唤醒
                    awaitCond.await();
                }
                System.out.print(s);
                state++;
                // 唤醒其它等待中的线程
                signalCond.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    public static void main(String[] args) {
        Thread threadA = new Thread(new Runnable() {
            @Override
            public void run() {
                function("A", 0, condA, condB);
            }
        });

        Thread threadB = new Thread(new Runnable() {
            @Override
            public void run() {
                function("B", 1, condB, condC);
            }
        });
        Thread threadC = new Thread(new Runnable() {
            @Override
            public void run() {
                function("C", 2, condC, condA);
            }
        });

        threadA.start();
        threadB.start();
        threadC.start();
    }
}