Hash version
Add HASH data structure, be familiar with the use of uthash, and add constraints.
Main constraints:
- Do not turn back, for example, if you turn forward once, you are not allowed to turn back
- Can't cross deands
- Add the termination condition and return once the target is matched
Do not turn back, for example, if you turn forward once, you are not allowed to turn back. If the initial state is 0000, turn once to 1000, and turn again to 0000. The main idea of the implementation is to add a hash table visit to record all existing traversal results. If the same results appear, they will be skipped and not recorded. It mainly uses the O(1) search function of uthash hash table to reduce the search time.
You can't cross deands, which is also a hash table lookup idea similar to visit. The time complexity of the hash table is O(1), but it changes space for time. Reducing time will increase space at the same time.
The termination condition, because the idea itself extends outward from the rotation number 0, is similar to a greedy algorithm idea, and each step is optimal. Once the target is matched, the current rotation number is the smallest, so it can be returned directly.
The hash version code submitted is as follows:
#define STR_SIZE 5 #define STR_LEN 4 int g_curLevelCnt; typedef struct HashTable { char str[STR_SIZE]; // key UT_hash_handle hh; // table head } StruHashTable; typedef struct QueList { int cnt; // Number of turns char *s; // Current password struct QueList *next; // Next possible password } StruQueList, *PtrStruQueList; char* AddOne(char *in, int j) { char *res = (char *)malloc(sizeof(char) * STR_SIZE); if (res == NULL) { return NULL; } memcpy(res, in, STR_SIZE); char ch = res[j]; if (ch == '9') { res[j] = '0'; return res; } res[j] = ch + 1; return res; } char* MinusOne(char *in, int j) { char *s = (char *)malloc(sizeof(char) * STR_SIZE); if (s == NULL) { return NULL; } memcpy(s, in, STR_SIZE); char ch = s[j]; if (ch == '0') { s[j] = '9'; return s; } s[j] = ch - 1; return s; } void Init(StruQueList **pQue, char *s, int cnt) { (*pQue) = (PtrStruQueList)malloc(sizeof(StruQueList)); (*pQue)->cnt = cnt; char *str = (char *)malloc(sizeof(char) * STR_SIZE); if (str == NULL) { return; } memcpy(str, s, STR_SIZE); (*pQue)->s = str; (*pQue)->next = NULL; g_curLevelCnt++; } // If it is greater than 0, the matching is successful and the number of turns is returned // If it is equal to 0, there is no exception // Less than 0 indicates an error int NodeExpand(StruQueList **queList, StruQueList **ptrQueListLastNode, char *target, StruHashTable **ptrDead, StruHashTable **ptrVisit) { int i; char *s; StruHashTable *hashTmp1, *hashTmp2; // Rotate once, and the current node evolves into 8 possibilities char *cur = (*queList)->s; int cnt = (*queList)->cnt; for (i = 0; i < 4; i++) { s = AddOne(cur, i); // If it matches the target if (strcmp(s, target) == 0) { // Termination conditions return cnt + 1; } // If in deands HASH_FIND(hh, *ptrDead, s, sizeof(char) * STR_SIZE, hashTmp1); // If traversed HASH_FIND(hh, *ptrVisit, s, sizeof(char) * STR_SIZE, hashTmp2); if (hashTmp1 == NULL && hashTmp2 == NULL) { // Not in dead, not in visit Init(&(*ptrQueListLastNode)->next, s, cnt + 1); *ptrQueListLastNode = (*ptrQueListLastNode)->next; hashTmp1 = (StruHashTable *)malloc(sizeof(StruHashTable)); // Add a hash node if (hashTmp1 == NULL) { return -1; } memcpy(hashTmp1->str, s, STR_SIZE); HASH_ADD(hh, *ptrVisit, str, sizeof(char) * STR_SIZE, hashTmp1); // printf("%s\n", s); } else { free(s); } s = MinusOne(cur, i); // If it matches the target if (strcmp(s, target) == 0) { // Termination conditions return cnt + 1; } // If in deands HASH_FIND(hh, *ptrDead, s, sizeof(char) * STR_SIZE, hashTmp1); // If traversed HASH_FIND(hh, *ptrVisit, s, sizeof(char) * STR_SIZE, hashTmp2); if (hashTmp1 == NULL && hashTmp2 == NULL) { // Not in dead, not in visit Init(&(*ptrQueListLastNode)->next, s, cnt + 1); *ptrQueListLastNode = (*ptrQueListLastNode)->next; hashTmp1 = (StruHashTable *)malloc(sizeof(StruHashTable)); // Add a hash node if (hashTmp1 == NULL) { return -1; } memcpy(hashTmp1->str, s, STR_SIZE); HASH_ADD(hh, *ptrVisit, str, sizeof(char) * STR_SIZE, hashTmp1); // printf("%s\n", s); } else { free(s); } } // printf("\n"); return 0; } // According to the method of queue and BFS, it is expressed only once at a time, and all the corresponding possibilities int openLock(char ** deadends, int deadendsSize, char * target) { char cur[STR_SIZE] = "0000"; // Initial value int i, ret; // special case if (strcmp(cur, target) == 0) { return 0; } // Initialize hash StruHashTable *dead = NULL; // The header is empty at the beginning StruHashTable *hashTmp; for (i = 0; i < deadendsSize; i++) { HASH_FIND(hh, dead, deadends[i], sizeof(char) * STR_SIZE, hashTmp); // Space occupied by key value sizeof(char) * 5 if (hashTmp == NULL) { // Not before hashTmp = (StruHashTable *)malloc(sizeof(StruHashTable)); // Add a hash node if (hashTmp == NULL) { return -1; } memcpy(hashTmp->str, deadends[i], STR_SIZE); HASH_ADD(hh, dead, str, sizeof(char) * STR_SIZE, hashTmp); // str represents the key value in the operation structure and is appended to hashtable } } // special case HASH_FIND(hh, dead, target, sizeof(char) * STR_SIZE, hashTmp); if (hashTmp != NULL) { return -1; // deanends contain target } HASH_FIND(hh, dead, cur, sizeof(char) * STR_SIZE, hashTmp); if (hashTmp != NULL) { return -1; // deanends contain target } // Initialize queue 0000 StruHashTable *visit = NULL; // The header is empty at the beginning PtrStruQueList queList, queListCurLevelLast, queListLastNode; queList = (PtrStruQueList)malloc(sizeof(StruQueList)); if (queList == NULL) { return -1; // if malloc is failed } g_curLevelCnt = 0; Init(&queList, cur, 0); hashTmp = (StruHashTable *)malloc(sizeof(StruHashTable)); // Add a hash node if (hashTmp == NULL) { return -1; } memcpy(hashTmp->str, queList->s, STR_SIZE); // printf("%p\n", visit); HASH_ADD(hh, visit, str, sizeof(char) * STR_SIZE, hashTmp); // Add a traversed state // printf("%p\n", visit); g_curLevelCnt = 0; queListLastNode = queList; ret = NodeExpand(&queList, &queListLastNode, target, &dead, &visit); if (ret > 0) { // Termination conditions return ret; } queListCurLevelLast = queListLastNode; queList = queList->next; // Iterate new possibilities from each node of all layers while (queList != NULL) { // Traverse all nodes of the current layer int len = g_curLevelCnt; g_curLevelCnt = 0; for (i = 0; i < len; i++) { ret = NodeExpand(&queList, &queListLastNode, target, &dead, &visit); if (ret > 0) { // Termination conditions return ret; } queList = queList->next; } } return -1; // Traverse all no matches }
Hash version optimization
Code simplification: delete redundant information and refine duplicate code into independent sub functions.
- Remove and merge duplicate codes to reduce redundancy
- Split more than 50 lines of code into sub function functions
- Replace the devil number with a meaningful macro name
- Optimize the while loop plus lastNode to judge as a for loop, and use the global variable to count the number of current layers as len
The main change codes are as follows:
void InitDeadHash(char **deadends, int deadendsSize, StruHashTable **ptrDead) { int i; StruHashTable *hashTmp; for (i = 0; i < deadendsSize; i++) { HASH_FIND(hh, *ptrDead, deadends[i], sizeof(char) * STR_SIZE, hashTmp); // Space occupied by key value sizeof(char) * 5 if (hashTmp == NULL) { // Not before hashTmp = (StruHashTable *)malloc(sizeof(StruHashTable)); // Add a hash node if (hashTmp == NULL) { return; } memcpy(hashTmp->str, deadends[i], STR_SIZE); HASH_ADD(hh, *ptrDead, str, sizeof(char) * STR_SIZE, hashTmp); // str represents the key value in the operation structure and is appended to hashtable } } return; } void InitQueAndVisitHash(char *cur, StruQueList **ptrQueList, StruHashTable **ptrVisit) { StruHashTable *hashTmp; *ptrQueList = (PtrStruQueList)malloc(sizeof(StruQueList)); if (*ptrQueList == NULL) { return; // if malloc is failed } g_curLevelCnt = 0; Init(ptrQueList, cur, 0); hashTmp = (StruHashTable *)malloc(sizeof(StruHashTable)); // Add a hash node if (hashTmp == NULL) { return; } memcpy(hashTmp->str, (*ptrQueList)->s, STR_SIZE); HASH_ADD(hh, *ptrVisit, str, sizeof(char) * STR_SIZE, hashTmp); // Add a traversed state return; } int DealCurStr(char *s, char *target, int cnt, StruQueList **ptrQueListLastNode, StruHashTable **ptrDead, StruHashTable **ptrVisit) { StruHashTable *hashTmp1, *hashTmp2; // If it matches the target if (strcmp(s, target) == 0) { // Termination conditions return cnt + 1; } // If in deands HASH_FIND(hh, *ptrDead, s, sizeof(char) * STR_SIZE, hashTmp1); // If traversed HASH_FIND(hh, *ptrVisit, s, sizeof(char) * STR_SIZE, hashTmp2); if (hashTmp1 == NULL && hashTmp2 == NULL) { // Not in dead, not in visit Init(&(*ptrQueListLastNode)->next, s, cnt + 1); *ptrQueListLastNode = (*ptrQueListLastNode)->next; hashTmp1 = (StruHashTable *)malloc(sizeof(StruHashTable)); // Add a hash node if (hashTmp1 == NULL) { return -1; } memcpy(hashTmp1->str, s, STR_SIZE); HASH_ADD(hh, *ptrVisit, str, sizeof(char) * STR_SIZE, hashTmp1); // printf("%s\n", s); } else { free(s); } return 0; } // If it is greater than 0, the matching is successful and the number of turns is returned // If it is equal to 0, there is no exception // Less than 0 indicates an error int NodeExpand(StruQueList *queList, StruQueList **ptrQueListLastNode, char *target, StruHashTable **ptrDead, StruHashTable **ptrVisit) { int i, ret; char *s; // Rotate once, and the current node evolves into 8 possibilities char *cur = queList->s; int cnt = queList->cnt; for (i = 0; i < 4; i++) { s = AddOne(cur, i); ret = DealCurStr(s, target, cnt, ptrQueListLastNode, ptrDead, ptrVisit); if (ret > 0) { return ret; } s = MinusOne(cur, i); ret = DealCurStr(s, target, cnt, ptrQueListLastNode, ptrDead, ptrVisit); if (ret > 0) { return ret; } } // printf("\n"); return 0; } int LevelTraverse(StruQueList *queList, StruQueList **ptrQueListLastNode, char *target, StruHashTable **ptrDead, StruHashTable **ptrVisit) { // Get the pointers of two queues, one is the current point, and the other is the new beginning corresponding to the level // Iterate new possibilities from each node of all layers int i, ret; while (queList != NULL) { // Traverse all nodes of the current layer int len = g_curLevelCnt; g_curLevelCnt = 0; for (i = 0; i < len; i++) { ret = NodeExpand(queList, ptrQueListLastNode, target, ptrDead, ptrVisit); if (ret > 0) { // Termination conditions return ret; } queList = queList->next; } // printf("\n\n"); } return 0; } // According to the method of queue and BFS, it is expressed only once at a time, and all the corresponding possibilities int openLock(char ** deadends, int deadendsSize, char * target) { char cur[STR_SIZE] = "0000"; // Initial value int ret; // special case if (strcmp(cur, target) == 0) { return 0; } // Initialize dead hash StruHashTable *dead = NULL; // The header is empty at the beginning StruHashTable *hashTmp, *hashTmp1, *hashTmp2; InitDeadHash(deadends, deadendsSize, &dead); // special case HASH_FIND(hh, dead, target, sizeof(char) * STR_SIZE, hashTmp1); HASH_FIND(hh, dead, cur, sizeof(char) * STR_SIZE, hashTmp2); if (hashTmp1 != NULL || hashTmp2 != NULL) { return -1; // deanends contain target } // Initialize queue 0000 and visit hash StruHashTable *visit = NULL; // The header is empty at the beginning PtrStruQueList queList, queListLastNode; InitQueAndVisitHash(cur, &queList, &visit); queListLastNode = queList; ret = LevelTraverse(queList, &queListLastNode, target, &dead, &visit); if (ret > 0) { // Termination conditions return ret; } return -1; // Traverse all no matches }
expectation
- Refer to the official solution to simplify the implementation of loop code in openLock()
- Bidirectional BFS can be used to further improve efficiency
- Heuristic search A * algorithm can be used to further improve efficiency