挂载OpenWrt系统到外部设备

背景 一般路由器提供的跟分区“/”比较小,当安装几个软件后就会导致磁盘空间不足,为了扩容我们可以挂载外部的存储设备,例如U盘、SD卡、移动硬盘等。下面我以GL.iNet-AR750路由器为例,演示如何挂载外部SD卡。首先你需要确保你的外部设备被正确的分区,我的SD卡上面被分了3个分区,分别存储SWAP、系统和其他文件。如果没有被正确的分区,请安装fdisk然后分区。SWAP需要文件系统类型为swap、存储文件的文件系统选择ext4。下面是我已经分区好后的设备名称以及他们的用途: /dev/sda1 SWAP SWAP /dev/sda2 / 系统 /dev/sda3 /mnt/sda3 其他 我们首先用mount命令查看已经挂载的设备和挂载点: 可以看到目前跟分区“/”由设备/dev/mtdblock6挂载,我们接下来需要把它替换成/dev/sda2。 操作 1. 安装依赖 opkg update opkg install fdisk (可选,如果你没有分区) opkg install block-mount 2. 拷贝系统到SD卡 注意,/mnt/sda2是你SD卡上用来存储系统的路径,依据你的实际情况修改。 3. 编辑ROM上的fstab文件 使用命令vi /etc/config/fstab修改文件的内容,这个fstab存储在路由器的ROM上而不是SD卡上。下面是我的路由器上面fstab中的内容,按照你自己的情况编辑,确保option target ‘/mnt/sda2’下面的option enabled设置为1,并且把挂载到/mnt/sda2的目录更改为/,然后增加两个option。然后删除swap和另一个mount配置,只保留global和挂载到/mnt/sda2的那一个配置。 修改前: 修改后: 如果你不小心搞坏了fstab文件,可以使用命令block detect > /etc/config/fstab来重新生成。 4. 重启后修改SD卡的fstab文件 重启后,修改的fstab将会生效。注意,现在我们有了两份fstab文件,一份存储在路由器的ROM中,另一份存储在SD卡中。系统首先读取ROM上的fstab文件来挂载块设备,然后会读取SD卡的fstab挂载块设备。因此当系统启动完成后,我们还需要修改SD卡的/etc/config/fstab文件。 修改前: 修改后: 然后再次重启系统,我们的修改已经彻底完成。使用df -h命令查看一挂载的分区和容量: 使用free命令查看SWAP: 5. 优化 – 修改swappness 我们希望当内存容量达到很高的阈值才使用swap,以防止系统因为频繁换页导致卡顿。修改文件/etc/sysctl.conf,追加vm.swappiness =…

Read more

139. 单词拆分

给定一个非空字符串 s 和一个包含非空单词列表的字典 wordDict,判定 s 是否可以被空格拆分为一个或多个在字典中出现的单词。 说明: 拆分时可以重复使用字典中的单词。 你可以假设字典中没有重复的单词。 示例 1: 输入: s = “leetcode”, wordDict = [“leet”, “code”]输出: true解释: 返回 true 因为 “leetcode” 可以被拆分成 “leet code”。 示例 2: 输入: s = “applepenapple”, wordDict = [“apple”, “pen”] 输出: true 解释: 返回 true 因为 “applepenapple” 可以被拆分成 “apple pen apple”。注意你可以重复使用字典中的单词。 示例 3: 输入: s = “catsandog”, wordDict =…

Read more

120. 三角形最小路径和

题目描述 给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。 例如,给定三角形: [ [2], [3,4], [6,5,7], [4,1,8,3] ] 自顶向下的最小路径和为 11(即,2 + 3 + 5 + 1 = 11)。 说明: 如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/triangle 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 解法1 这道题目使用dp来求解,通过之前求得小规模的问题计算更大规模的问题。题目提到“每一步只能移动到下一行中相邻的结点上”,我们把题目给定的样例按照最左端对其,我们将这个数组记为triangle。 [ [2], [3,4], [6,5,7], [4,1,8,3] ] 我们在triangle数组中找一个比较general的case,对于元素8,它的上一行只有5和7能够走到8。也就是说当元素处于第i行第j列时(i,j从0计数),只能从i-1, j-1与i-1, j的位置走来。我们就可以定义数组dp[i][j]表示自顶向下走到第i, j位置时最小路径和,有dp[i][j] = min(dp[i-1][j-1], dp[i-1][j]) + triangle[i][j]。 需要注意一点的是越界处理。当i=0,j=0时,自顶向下只有triangle[0][0]一个元素,取dp[i][j] = trangle[i][j];当j=0时,dp[i-1][j-1]是越界的,取dp[i][j] = dp[i-1][j] + triangle[i][j];当j=i时,dp[i-1][j]是越界的,取dp[i][j] = dp[i-1][j-1] + triangle[i][j]。 在实现时,我们采用Bottom-up的解法,声明与triangle数组同型(大小一致)的数组dp,递增变量i,j,将变量带入公式循环更新dp数组,最终的答案为min(dp[dp.length – 1])。在实现中,为了避免扫描dp[dp.length – 1]来求最小值,我们使用临时变量来存储当前遇到的最小值。 解法1优化 题目提到“如果你可以只使用 O(n) 的额外空间(n 为三角形的总行数)来解决这个问题,那么你的算法会很加分。”,下面我们就来优化解法1的空间复杂度。通过观察dp公式,可以看出dp[i][j]仅依赖于dp[i-1][j-1]与dp[i-1][j]。那我们可以声明大小为dp[2][|triangle|]的数组,循环使用dp[0]与dp[1]。dp[0]表示上一轮计算结果,dp[1]表示本轮计算结果,当完成一轮迭代后交换dp[0]与dp[1]。

Read more

70. 爬楼梯

题目描述 假设你正在爬楼梯。需要 n 阶你才能到达楼顶。 每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢? 注意:给定 n 是一个正整数。 示例 1: 输入: 2输出: 2解释: 有两种方法可以爬到楼顶。 1 阶 + 1 阶 2 阶 示例 2: 输入: 3输出: 3解释: 有三种方法可以爬到楼顶。 1 阶 + 1 阶 + 1 阶 1 阶 + 2 阶 2 阶 + 1 阶 来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/climbing-stairs著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 解法1 这是一道简单的DP题目,是DP的应用之一 – 计数。给定一个台阶数目n,我们可以从n-1节台阶跳一步到达第n节;也可以从n-2跳两步到达第n节。我们把到达第n节台阶的爬楼梯方法记为dp[n],那么有dp[n] = dp[n-1]…

Read more

Java线程池概述

内容提要 最近,在公司做了几次技术分享,将slides上的内容整理成博客。本文的主要内容包括如下几个部分: 背景 Java线程池类型 线程池参数与使用实例 ThreadPoolExecutor实现 总结 本文将主要讲解ThreadPoolExecutor的使用、参数解释以及内部实现,对于ScheduedThreadPoolExecutor与ForkJoinPool只会简单提及。 一、背景 我们为什么需要使用线程池,直接new Thread岂不是很方便?如果我们在编写Demo或者开发个小型的Java应用程序,那么这种方法的确没什么问题。但是如果我们在开发大型的企业级应用,那么直接创建线程是一种糟糕的实践,会造成如下问题: 直接new Thread使创建线程的代码分散在项目各个地方,不利于维护(指代码上的) 当你的Runnable执行完毕线程无法复用造成资源浪费 直接new Thread不遍于控制线程的数量(启动多少个线程不受限制,想new就new) 那么这些问题有什么缺点呢?对于第一条是显然的。对于第二条,你可能以为创建线程的代价和创建一个对象的代价差不多,但除了创建对象外,还可能会在OS层面创建线程(这取决于JVM实现)。线程是一种宝贵的资源(每个线程有自己的调用栈、PC、IR),频繁创建销毁开销很大。对于第三条,创建大量的线程显而易见会带来昂贵的调度开销,要知道线程调度需要做保留现场、恢复现场等一些列操作。 二、Java线程池类型 为了实现不同类型的线程池,定义了三个接口:Executor、 ExecutorService 、ScheduledExecutorService接口,他们都位于java.util.concurrent包下。 Executor:能够执行Runnable的简单接口,只含有execute一个方法 ExecutorService :继承自Executor,添加了shutdown、submit、invokeAll等方法,以支持异步执行 ScheduledExecutorService:继承自ExecutorService,支持周期性地执行任务 Java线程池目前有三个实现,分别是ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool。ThreadPoolExecutor可以根据构造方法实现不同类型的线程池(或者使用Executors类),但是不支持定时任务;ScheduledExecutorService支持定时执行;ForkJoinPool利用Fork-join框架,实现了work-stealing,对于执行时间差异很大的任务使用该类型线程池能够实现负载均衡。图1展示了他们直接的继承/实现关系。 三、线程池参数与使用实例 下面我们分别介绍这三个线程池的构造方法,这样我们才能够知道如何创建线程池。因为每种线程池都有多个构造方法,我们只介绍参数最复杂的那一个。 1. ThreadPoolExecutor 构造方法 corePoolSize: 当线程池中线程数量小于corePoolSize, 即使这些线程是空闲的,也不会被销毁。但如果调用方法allowCoreThreadTimeOut(true),表示核心线程池的线程在没有任务到达的时候,keepAliveTime时间后销毁。 maximumPoolSize:限定线程池最大数量。当线程池中线程数达到corePoolSize时,且任务队列workQueue以及满了,就会创建新线程直到数量达到maxiumPoolSize。注意,如果workQueue是一个无界队列(unbounded)时,该参数是无效的,因为你无论添加多少任务workQueue都不会满。 keepAliveTime:当线程数大于corePoolSize时,多余线程的存活时间。 unit:keepAliveTime参数的时间单位 workQueue:用于存放尚未执行任务的队列,元素必须是Runnable。workQueue必须是一个阻塞队列,例如LinkedBlockingQueue、ArrayBlockingQueue。 threadFactory:创建线程时所使用的线程工厂,默认为Executors.defaultThreadFactor。如果你想让创建的线程带有自定义name或者优先级时,可以传入自己实现的线程工厂。 handler:在某种情况下(下面会提到),无法提交任务所执行的处理策略。目前有如下内置的handler实现。 AbortPolicy 直接拒绝新任务,并抛出RejectedExecutionException异常 CallerRunsPolicy 用当前线程的execute方法执行被拒绝的任务,如果执行器已经关闭则丢弃任务 DiscardPolicy 默默地丢弃新到的任务 DiscardOldestPolicy 丢弃最老的一个未执行的任务并执行当前任务 线程池逻辑 图2描述了线程创建与入队的逻辑。如果从线程创建和入队的角度分别观察,能够整理出如下规则。 线程创建规则 当线程池中线程数量小于corePoolSize时,将会创建新线程(即使之前创建当线程处于空闲) 如果线程数量大于等于corePoolSize且小于maximumPoolSize时,只有队列已满才会创建新线程 入队规则 如果线程数量小于corePoolSize,优先创建线程而不入队…

Read more

312. 戳气球

题目描述 有 n 个气球,编号为0 到 n-1,每个气球上都标有一个数字,这些数字存在数组 nums 中。 现在要求你戳破所有的气球。每当你戳破一个气球 i 时,你可以获得 nums[left] * nums[i] * nums[right] 个硬币。 这里的 left 和 right 代表和 i 相邻的两个气球的序号。注意当你戳破了气球 i 后,气球 left 和气球 right 就变成了相邻的气球。 求所能获得硬币的最大数量。 说明: 你可以假设 nums[-1] = nums[n] = 1,但注意它们不是真实存在的所以并不能被戳破。 0 ≤ n ≤ 500, 0 ≤ nums[i] ≤ 100 示例: 输入: [3,1,5,8] 输出: 167 解释: nums = [3,1,5,8] –> [3,5,8] –> [3,8] –> [8] –> []   coins = 315 + 358…

Read more

174. 地下城游戏

题目描述 一些恶魔抓住了公主(P)并将她关在了地下城的右下角。地下城是由 M x N 个房间组成的二维网格。我们英勇的骑士(K)最初被安置在左上角的房间里,他必须穿过地下城并通过对抗恶魔来拯救公主。 骑士的初始健康点数为一个正整数。如果他的健康点数在某一时刻降至 0 或以下,他会立即死亡。 有些房间由恶魔守卫,因此骑士在进入这些房间时会失去健康点数(若房间里的值为负整数,则表示骑士将损失健康点数);其他房间要么是空的(房间里的值为 0),要么包含增加骑士健康点数的魔法球(若房间里的值为正整数,则表示骑士将增加健康点数)。 为了尽快到达公主,骑士决定每次只向右或向下移动一步。 编写一个函数来计算确保骑士能够拯救到公主所需的最低初始健康点数。 例如,考虑到如下布局的地下城,如果骑士遵循最佳路径 右 -> 右 -> 下 -> 下,则骑士的初始健康点数至少为 7。 -2 (K) -3 3 -5 -10 1 10 30 -5 (P) 说明: 骑士的健康点数没有上限。 任何房间都可能对骑士的健康点数造成威胁,也可能增加骑士的健康点数,包括骑士进入的左上角房间以及公主被监禁的右下角房间。 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/dungeon-game 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 解法1 – DP 题目给定的数组dungeon存放了走入每个房间造成的血量变化,为了救出公主,要求到达右下角(P)时扣除外血量至少为1,因此我们可以从P点逆行回到起点K,求出起点K要求的最少血量。我们定义一个二维数组dp[|dungeon|+1][|dungeon[0]|+1],dp数组比原数组“大了一圈“,这样做能够方便我们统一地处理逻辑,否则需要判断边界条件。到达P点以后还会扣除或者增加血量,所以走过P点到达右方或下方的血量至少需要1。 图1展示了题目的例子计算dp数组后的结果。要想求出到达位置i,j需要的最少血量,那么可以从位置i+1,j与i,j+1逆推而来。例如dp[2][1]=1,dp[1][2]=5,dungeon[1][1] = -10,说明我们只需要在位置1,1有血量11,就能够到达[2][1]。还有一点要注意,血量不可能为负数。例如在2,1处能够加血30,那我们不能说到达2,1时只需要-29点血量,然后再给我加上30变成1,这是不对的,血量不能为负值。所以在计算完min(dp[i+1][j], dp[i][j+1])+dungeon[i][j]还需要对1取最大。 该解法时间复杂度为O(m*n),m与n为dungeon数组长与宽。全部代码如下:

Read more

39. 组合总和

题目描述 给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的数字可以无限制重复被选取。 说明: 所有数字(包括 target)都是正整数。 解集不能包含重复的组合。  示例 1: 输入: candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ] 示例 2: 输入: candidates = [2,3,5], target = 8, 所求解集为: [   [2,2,2,2],   [2,3,3],   [3,5] ] 来源:力扣(LeetCode) 链接:https://leetcode-cn.com/problems/combination-sum 著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 解法1 在上一篇文章“代码模版-排列与组合”我们介绍了排列与组合的实现方法,在这道题中,我们将会在组合算法上进行一些修改来解这道题目。我们先观察下组合算法的实现。 在这道题目中,要求数字可以重复选取,但是上面的代码每次都会选取新的一个数字。我们将递归调用combination(arr, depth + 1, i + 1, curr, result)改成combination(arr, depth + 1, i, curr,…

Read more

代码模版 – 排列与组合

有的时候题目想不出来,或者只能用搜索来解决(如Leetcode – 39题),我们就可以用排列/组合搜索答案。下面的排列与组合模板都使用DFS来实现,非常易懂且方便记忆。 1. 排列 方法private void permutation(int[] arr, int depth, int[] curr, boolean[] visited, List<int[]> result)实现了求arr的所有排列,并且将结果保存的result中。depth表示目前正在填充curr的第几位,visited数组记录了之前已经访问的元素。在for循环中,我们遍历每个之前没有访问过位置,并在visited标记为true,防止下一次递归调用访问同一元素。当depth达到数组arr的长度时,我们复制curr的内容到result中。 2. 组合 这里的组合指的是从n个元素中挑选c个元素,而不考虑元素的顺序。方法void combination(int[] arr, int depth, int start, int[] curr, List<int[]> result)实现了从arr中挑选|curr|个元素。depth表示我们正在填充curr的第几位,start参数表明我们从arr中第几个位置开始挑选元素。与排列算法相比,我们不需要visited数组。因为我们总是从start之后挑选元素,所以不会出现挑选重复元素的情况。

Read more

307. 区域和检索 – 数组可修改

题目描述 给定一个整数数组  nums,求出数组从索引 i 到 j  (i ≤ j) 范围内元素的总和,包含 i,  j 两点。 update(i, val) 函数可以通过将下标为 i 的数值更新为 val,从而对数列进行修改。 示例: Given nums = [1, 3, 5] sumRange(0, 2) -> 9update(1, 2)sumRange(0, 2) -> 8说明: 数组仅可以在 update 函数下进行修改。 你可以假设 update 函数与 sumRange 函数的调用次数是均匀分布的。 来源:力扣(LeetCode)链接:https://leetcode-cn.com/problems/range-sum-query-mutable著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。 解法1 – Prefix sum 我们可以预先计算出前缀和存储在数组中,prefix[i] = num[0]+…+nums[i]。这样,当需要求得范围[i,j]的和时,我们可以通过prefix[j]-prefix[i-1]在O(1)的时间计算出结果。但是当nums[i]发生更新操作,那么我们不得不重新计算prefix[i]之后的前缀和,时间复杂度为O(n)。 全部代码如下: 解法2 – Fenwick Tree(树状数组) Fenwick Tree也称为树状数组,它可以在O(logn)的时间得到任意前缀和,并同时支持在O(logn)时间内支持动态单点值的修改。空间复杂度O(n)。为了实现Fenwick Tree,我们需要一个函数lowbit(x),他是用来求得数值x最低位第一个1所代表到数值,例如x = 0110,lowbit(x) = 0010; x = 0101, lowbit(x) = 0001。…

Read more