算法-思维训练

设计一个超时功能

思路:

  1. 开一个watchdog线程,用来监控超时,如果超时通知外部
  2. 为提升程序性能,在等待超时的时间段内,watchdog线程要阻塞
  3. 超时链表管理可以放在伴生对象中实现
class ATimeOut constructor(private val timeoutNanos: Long) {
    
    

    /** 记录超时时间点 */
    private var timeoutAt = 0L
    /** 下一个节点 */
    private var next: ATimeOut? = null

    /**
     * 开始计时
     */
    fun enter() {
    
    
        val timeoutNanos = timeoutNanos
        scheduleTimeout(this, timeoutNanos)
    }

    /** 
     * 通知使用方超时 
     */
    protected open fun timedOut() {
    
    }

    /**
     * 剩余时间
     */
    private fun remainingNanos(now: Long) = timeoutAt - now

    /**
     * watchdog子线程
     */
    private class Watchdog internal constructor() : Thread("watchdog") {
    
    
        init {
    
    
            isDaemon = true
        }

        override fun run() {
    
    
            while (true) {
    
    
                var timeout: ATimeOut? = null
                synchronized(ATimeOut::class.java) {
    
    
                    /** 获取超时节点,等待期间线程阻塞 */
                    timeout = awaitTimeout()

                    // 链表为空,线程退出
                    // 下次schedule时,会创建一个新的线程
                    if (timeout === head) {
    
    
                        head = null
                        return
                    }
                    
                    timeout?.timedOut()
                }
            }
        }
    }

    /**
     * 伴生对象负责:
     *  1. 维护将新节点链入链表逻辑
     *  2. 从链表中取出超时节点逻辑
     */
    companion object {
    
    

        private val IDLE_TIMEOUT_MILLIS = TimeUnit.SECONDS.toMillis(60)
        private val IDLE_TIMEOUT_NANOS = TimeUnit.MILLISECONDS.toNanos(IDLE_TIMEOUT_MILLIS)

        /** 链表头 */
        private var head: ATimeOut? = null

        /**
         * 将新节点链入链表当中
         * 链表按超时时间排序
         */
        private fun scheduleTimeout(node: ATimeOut, timeoutNanos: Long) {
    
    
            synchronized(ATimeOut::class.java) {
    
    
                if (head == null) {
    
    
                    head = ATimeOut(-1)
                    /** 开启watchdog线程 */
                    Watchdog().start()
                }
                val now = System.nanoTime()
                node.timeoutAt = now + timeoutNanos
                val remainingNanos = node.remainingNanos(now)
                var prev = head!!
                /** 链表按 超时时间排序 */
                while (true) {
    
    
                    if (prev.next == null || remainingNanos < prev.next!!.remainingNanos(now)) {
    
    
                        node.next = prev.next
                        prev.next = node
                        if (prev === head) {
    
    
                            /** 唤醒watchdog线程 */
                            (ATimeOut::class.java as Object).notify()
                        }
                        break
                    }
                    prev = prev.next!!
                }
            }
        }

        /**
         * 从链表取出超时节点,并从链表中将节点删除
         */
        internal fun awaitTimeout(): ATimeOut? {
    
    
            val node = head!!.next
            if (node == null) {
    
    
                val startNanos = System.nanoTime()
                // 如果队列为空,watchdog线程等60s,60s后还为空,线程退出
                (ATimeOut::class.java as Object).wait(IDLE_TIMEOUT_MILLIS)
                // 线程被唤醒后,需要判断
                return if (head!!.next == null && System.nanoTime() - startNanos >= IDLE_TIMEOUT_NANOS) {
    
    
                    head
                } else {
    
    
                    null
                }
            }
            var waitNanos = node.remainingNanos(System.nanoTime())
            // 线程阻塞
            if (waitNanos > 0) {
    
    
                val waitMillis = waitNanos / 1000000L
                waitNanos -= waitMillis * 1000000L
                (ATimeOut::class.java as Object).wait(waitMillis, waitNanos.toInt())
                return null
            }

            // 走到这里,代表节点超时,将节点删除
            head!!.next = node.next
            node.next = null
            return node
        }
    }
}

两个链表想加

思路:

  1. 需要一个carry变量用于保存进位;
  2. 最后退出循环,还需再判断一下carry
public ListNode addLinkedList(ListNode l1, ListNode l2) {
    
    
    ListNode dummyHead = new ListNode(0);
    ListNode curr = dummyHead;
    // p,q两个移动指针
    ListNode p = l1, q = l2;
    int carry = 0;
    while (p != null || q != null) {
    
    
        int x = (p != null) ? p.val : 0;
        int y = (q != null) ? q.val : 0;
        int sum = carry + x + y;
        // 计算进位
        carry = sum / 10;
        // 构建新节点,注意要和10取模
        curr.next = new ListNode(sum % 10);
        curr = curr.next;
        if (p != null) p = p.next;
        if (q != null) q = q.next;
    }
    if (carry > 0) {
    
    
        curr.next = new ListNode(carry);
    }
    return dummyHead.next;
}

查找无重复最长子串

思路:

  1. 需要两个指针,表示子串的左右边界
  2. 在查找过程中,借助数据结构Set存储子串并判断是否有发生重复
public int lengthOfLongestSubstring(String s) {
    
    
        // 哈希集合,记录每个字符是否出现过
        Set<Character> set = new HashSet<Character>();
        // 右指针,初始值为 -1,相当于我们在字符串的左边界的左侧,还没有开始移动
        int right = -1, result = 0;
        int n = s.length();
        for (int left = 0; left < n; left++) {
    
    
            if (left != 0) {
    
    
                // 左指针向右移动一格,移除一个字符
                set.remove(s.charAt(left - 1));
            }
            while (right + 1 < n && !set.contains(s.charAt(right + 1))) {
    
    
                // 不断地移动右指针
                set.add(s.charAt(right + 1));
                right++;
            }
            // 第 left 到 right 个字符是一个极长的无重复字符子串
            result = Math.max(result, right - left + 1);
        }
        return result;
    }

顺时针旋转矩阵

思路:

  1. 第一步:转置矩阵
  2. 第二步:每一行反转
public void rotate(int[][] matrix) {
    
    
	int n = matrix.length;
	
	// 转置
	for (int i = 0; i < n; i++) {
    
    
		for (int j = i; j < n; j++) {
    
    
			int tmp = matrix[j][i];
			matrix[j][i] = matrix[i][j];
			matrix[i][j] = tmp;
		}
	}

	// 反转每行
	for (int i = 0; i < n; i++) {
    
    
		int start = 0;
		int end = n;
		while (start < end) {
    
    
			int tmp = matrix[i][start];
			matrix[i][start] = matrix[i][end];
			matrix[i][end] = tmp;
			start++;
			end--;
		}
	}
}

在数组中找三个数,其和最接近target

思路:

  1. 如果暴力查找,要三层循环
  2. 两层循环,利用双指针减少一层循环,前提需要排下序
public int threeCloseNumber(int[] nums, int target) {
    
    
    // 排序
    Arrays.sort(nums);

    int result = nums[0] + nums[1] + nums[2];
    for (int i = 0; i < nums.length - 2; i++) {
    
    
        // 左右两个指针
        int left = i + 1;
        int right = nums.length - 1;

        while (left != right) {
    
    
            int sum = nums[0] + nums[left] + nums[right];
            if (Math.abs(sum - target) < Math.abs(result - target)) {
    
    
                result = sum;
            }
            // 如果比target大,右指针左移
            if (sum > target) {
    
    
                right--;
            } else {
    
    
                left++;
            }
        }
    }

    return result;
}

在数组中找出所有三元组,其和为0

思路:

  1. 最容易想到:三层循环,暴力查找
  2. 两层循环。需要借助辅助空间,暂存遍历过的数据
public List<List<Integer>> threeNum(int[] nums) {
    
    
    // 存储所有三元组    
    Set<List<Integer>> result = new LinkedHashSet<>();   
    for (int i = 0; i < nums.length - 2; i++) {
    
    
        int target = 0 - nums[i];
        Map<Integer, Integer> map = new HashMap<>();
        for (int j = i + 1; j < nums.length; j++) {
    
    
            int v = target - nums[j];
            Integer exist = map.get(v);
            // 找到一个三元组
            if (exist != null) {
    
    
                List<Integer> list = Arrays.asList(nums[i], exist, nums[j]);
                result.add(list);
            } else {
    
    
                // 存储数据
                map.put(nums[j], nums[j]);
            }
        }
    }
    return result;
}

おすすめ

転載: blog.csdn.net/H_Zhang/article/details/106113607