判断正整数各个数位上的数字关系(算法)
问题描述:
示例:
解题思路:
- 首先解决输入的数据和各组数据的上下边界:
1 // main method 2 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); 3 String NStr = br.readLine(); 4 int N = Integer.parseInt( NStr ); 5 6 int[] aN = new int[ N ]; 7 int[] bN = new int[ N ]; 8 // 读取各组数据 9 for( int i = 0; i < N; i++ ) { 10 String[] data = br.readLine().split(" "); 11 aN[i] = Integer.parseInt( data[0] ); // 区间的左边界 12 bN[i] = Integer.parseInt( data[1] ); // 区间的右边界 13 }
- 接下来对 [A, B] 区间进行遍历,对该区间内的所有数进行检查(调用 check 函数),如果返回的 score 比现有的 score 大,则更新现有的 score:
1 public static int find( int A, int B ) { 2 int score = 0; 3 4 for( int n = A; n <= B; n++ ) { 5 String nStr = String.valueOf( n ); 6 int tmpScore = check( nStr ); // 检查 n 字符串是不是 cc number,并且返回它的 score 7 if( tmpScore > score ) 8 score = tmpScore; 9 } 10 return score; // 返回的是 A-B 区间内的 CC Number 的最大 Score 11 }
- 核心算法,检查这个数是否为 CC number,若是,则计算并返回它的 score:
1 public static int check( String nStr ) { 2 char cp, cb; // pre, back 3 int score = 0; 4 int up1Start = -1, down1Start = -1, down1End = -1; 5 int up2Start = -1, down2Start = -1; 6 boolean isCCNumber = false; 7 for( int j = 0; j < nStr.length() - 1; j++ ) { 8 cp = nStr.charAt( j + 1 ); 9 cb = nStr.charAt( j ); // current char 10 11 if( up1Start == -1 ) { 12 if( cp > cb) { 13 // start up1 14 up1Start = j; 15 } 16 } 17 else { 18 if( down1Start == -1 ) { 19 // on up1 20 if( cp == cb ) { 21 up1Start = -1; // reset up1start 22 } 23 else if( cp < cb ) { 24 // start down1 25 down1Start = j; 26 } // no need to process case cp > cb again 27 28 } 29 else { 30 if( down1End == -1 ) { 31 // on down1 32 if (cp >= cb) { 33 down1End = j; // finish down1 34 } // no need to process case cp < cb 35 } 36 else { 37 // check up-down second time 38 if( up2Start == -1 ) { 39 if( cp > cb && cb != '0' ) { 40 // start up2, cb can not be '0' 41 up2Start = j; 42 if( up2Start - down1End != 1 ) { 43 up1Start = -1; 44 down1Start = -1; 45 down1End = -1; 46 up2Start = -1; 47 continue; 48 } 49 } 50 } 51 else { 52 if( down2Start == -1 ) { 53 // on up2 54 if( cp == cb ) { 55 up2Start = -1; // reset up1start 56 } 57 else if( cp < cb ) { 58 // start down2 59 down2Start = j; 60 isCCNumber = true; 61 break; // no need to know where down2 ends 62 } // no need to process case cp > cb again 63 64 } 65 // else { 66 // if( down2End == -1 ) { 67 // // on down2 68 // if (cp >= cb) { 69 // down2End = j; 70 // } // no need to process case cp < cb 71 // } 72 // } 73 } 74 } 75 } 76 } 77 } 78 if( isCCNumber ) { 79 score = calcScore( nStr ); 80 } 81 82 return score; 83 }
- 计算 score 的函数,只需要将该数字对应的字符串上每个字符与 '0' 字符的差值相加即可:
1 public static int calcScore( String nStr ) { 2 char c; 3 int score = 0; 4 for( int i = 0; i < nStr.length(); i++ ) { 5 c = nStr.charAt( i ); 6 score += ( c - '0' ); 7 } 8 return score; 9 }
完整代码:
https://github.com/BriFuture/blog-code-example/blob/master/18-06to09/ccnumber/Main.java
核心步骤算法(check 方法)说明:
对于每个给定的数,要判断它是否为 CC number,有以下步骤:
从左往右(index 从 0 到 length - 1)依次判断相邻位数上的数字大小关系(循环),将当前数位上的数位 numCurr(对应代码中的 nb),下一个数位上的数标记为 numNext(对应代码中的 np)
每次循环从第 1 步开始:
- 若 up1Start 尚未被标记:
- 判断 numNext > numCurr,若为 true 说明开始处于第一段 up 区域,将 up1Start 记为当前索引,执行第 2 步;
若 up1Start 已被标记,执行第 2 步; - 如果 down1Start 尚未被标记,判断 numNext 与 numCurr 的关系:
- 若 numNext == numCurr,清除 up1Start 的标记,返回第 1 步;
- 若 numNext < numCurr,将 down1Start 记为当前索引,执行第 3 步;
如果 down1Start 已被标记,执行第 3 步; - 如果 down1End 尚未标记判断 numNext 与 numCurr 的关系:
- 若 numNext >= numCurr,将 down1End 记为当前索引,执行第 4 步;
- 否则继续执行第 3 步;
如果 down1End 已被标记,执行第 4 步, - 如果 up2Start 尚未标记,进行判断:
- 若 numNext > numCurr 并且 numCurr 不为 0(C number 的最高位不能为 0),
判断当前索引与 down1End 的差值,
* 若差值不为 1,则需要重置所有标志位,执行第 1步,
* 否则执行第 5 步;
- 否则继续执行第 4 步;
否则执行第 5 步; - 如果 down2Start 尚未标记,判断 numNext 与 numCurr 的关系:
若 numNext == numCurr,清除 up2Start 的标记,返回第 4 步;
若 numNext < numCurr,将 down2Start 标记为当前索引,标记 isCCNumber 为 true,跳出循环;
否则继续执行第 5 步;
上述步骤结束后,判断 isCCnumber,若为真的话说明这个数字是 CC number,计算它的 score 并返回,否则返回 0。
关于算法的思考,
做题的时候没有仔细思考这个题目,思路比较直接,其实这个位置的解法步骤有点复杂,实际上可以把它当做有限状态机来做:
1 public enum State { Start, Stage1Up,Stage1Down, StageNext, Stage2Up, /*Stage2Down*/}; 2 public static int checkWithFSM( String nStr ) { 3 State currState = State.Start; 4 char cp, cb; // pre, back 5 int j = 0; 6 while( j < nStr.length() - 1 ) { 7 cp = nStr.charAt( j + 1 ); // next char 8 cb = nStr.charAt( j ); // current char 9 switch ( currState ) { 10 case Start: 11 if( cp > cb ) 12 currState = State.Stage1Up; // state State1Up 13 else 14 currState = State.Start; 15 break; 16 case Stage1Up: 17 if( cp == cb ) 18 currState = State.Start; // reset 19 else if( cp < cb ) 20 currState = State.Stage1Down; // end of Stage1Up 21 // else Stage1Up 22 break; 23 case Stage1Down: 24 if( cp >= cb ) { 25 currState = State.StageNext; // end of Stage1Down 26 } 27 // else Stage1Down 28 break; 29 case StageNext: 30 if( cp > cb && cb != '0' ) // maximum number can not be '0' 31 currState = State.Stage2Up; // start State2Up 32 else 33 currState = State.Start; // Stage1Down 和 Stage2Up 不相邻,reset 34 break; 35 case Stage2Up: 36 if( cp == cb ) { 37 currState = State.Start; // not on up, reset currState 38 } 39 else if( cp < cb ) { 40 // currState = State.Stage2Down; // start down 41 return calcScore( nStr ); 42 } 43 // else Stage2Up 44 break; 45 // case Stage2Down: // no need to care about Stage2Down state 46 47 } 48 j++; 49 } 50 51 return 0; 52 }
换成 FSM 来做之后流程更加清楚了,具体细节也就不再赘述,流程图如下:
回顾问题:
将数字 n 上各个数位当作横坐标(离散取值,范围从 0 到 length - 1),各个数位的数字(0-9)当作纵坐标(离散取值,范围从 0 到 9),可以得到一个离散取点的二维平面图,平面图的走势为先增后减再增再减即可,
需要注意的是第一次下降结束的位置和第二次上升开始的位置重合:
如果连接各个离散的点,up-down,up-down趋势的数据就是符合要求的。
本博客由 BriFuture 原创,并在个人博客(WordPress构建) BriFuture's Blog 上发布。欢迎访问。
欢迎遵照 CC-BY-NC-SA 协议规定转载,请在正文中标注并保留本人信息。